/*!	 canvasview.cpp
**	 Template File
**
**	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
**	Copyright (c) 2007, 2008 Chris Moore
**	Copyright (c) 2009, 2011, 2012 Carlos López
**	Copyright (c) 2009, 2011 Nikita Kitaev
**	Copyright (c) 2012 Konstantin Dmitriev
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
*/

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include <synfig/general.h>

#include <sstream>
#include <algorithm>
#include <cmath>

#include <gtkmm/paned.h>
#include <gtkmm/scale.h>
#include <gtkmm/dialog.h>
#include <gtkmm/messagedialog.h>
#include <gtkmm/treemodelsort.h>
#include <gtkmm/button.h>
#include <gtkmm/buttonbox.h>
#include <gtkmm/separator.h>
#include <gtkmm/eventbox.h>
#include <gtkmm/label.h>
#include <gtkmm/box.h>
#include <gtkmm/menu.h>
#include <gtkmm/menuitem.h>
#include <gtkmm/imagemenuitem.h>
#include <gtkmm/separatormenuitem.h>
#include <gtkmm/toolitem.h>
#include <gtkmm/toolbutton.h>
#include <gtkmm/toggletoolbutton.h>
#include <gtkmm/separatortoolitem.h>
#include <gtkmm/menutoolbutton.h>

#include <glibmm/uriutils.h>
#include <glibmm/convert.h>

#include <gtk/gtk.h>

#include <gdk/gdk.h>

#include <synfig/valuenodes/valuenode_reference.h>
#include <synfig/valuenodes/valuenode_subtract.h>
#include <synfig/valuenodes/valuenode_linear.h>
#include <synfig/valuenodes/valuenode_timedswap.h>
#include <synfig/valuenodes/valuenode_scale.h>
#include <synfig/valuenodes/valuenode_range.h>
#include <synfig/valuenodes/valuenode_dynamiclist.h>
#include <synfig/valuenodes/valuenode_twotone.h>
#include <synfig/valuenodes/valuenode_stripes.h>
#include <synfig/valuenodes/valuenode_blinecalctangent.h>
#include <synfig/valuenodes/valuenode_blinecalcvertex.h>
#include <synfig/valuenodes/valuenode_blinecalcwidth.h>
#include <synfig/valuenodes/valuenode_bline.h>
#include <synfig/valuenodes/valuenode_bone.h>
#include <synfig/layer.h>
#include <synfig/layers/layer_pastecanvas.h>
#include <synfig/context.h>

#include <synfigapp/uimanager.h>
#include <synfigapp/canvasinterface.h>
#include <synfigapp/selectionmanager.h>

#include "canvasview.h"
#include "instance.h"
#include "app.h"
#include "cellrenderer/cellrenderer_value.h"
#include "cellrenderer/cellrenderer_timetrack.h"
#include "workarea.h"
#include "dialogs/dialog_color.h"
#include "eventkey.h"

#include "states/state_normal.h"

#include "ducktransform_scale.h"
#include "ducktransform_translate.h"
#include "ducktransform_rotate.h"

#include "event_mouse.h"
#include "event_layerclick.h"

#include "mainwindow.h"
#include "docks/dockmanager.h"
#include "docks/dockbook.h"

#include "dialogs/dialog_preview.h"
#include "dialogs/dialog_soundselect.h"

#include "preview.h"
#include "audiocontainer.h"
#include "widgets/widget_timeslider.h"
#include "widgets/widget_enum.h"
#include "dials/keyframedial.h"
#include "dials/jackdial.h"

#include <synfigapp/main.h>
#include <synfigapp/inputdevice.h>

#include <pangomm.h>

#include <gui/localization.h>

#endif

using namespace std;
using namespace etl;
using namespace synfig;
using namespace studio;
using namespace sigc;

#define DEFAULT_TIME_WINDOW_SIZE		(10.0)

#ifndef SMALL_BUTTON
#define SMALL_BUTTON(button,stockid,tooltip)	\
	button = manage(new class Gtk::Button());	\
	icon=manage(new Gtk::Image(Gtk::StockID(stockid),iconsize));	\
	button->add(*icon);	\
	button->set_tooltip_text(tooltip);\
	icon->set_padding(0,0);\
	icon->show();	\
	button->set_relief(Gtk::RELIEF_NONE); \
	button->show()
#endif

#ifndef NORMAL_BUTTON
#define NORMAL_BUTTON(button,stockid,tooltip)	\
	button = manage(new class Gtk::Button());	\
	icon=manage(new Gtk::Image(Gtk::StockID(stockid),Gtk::ICON_SIZE_BUTTON));	\
	button->add(*icon);	\
	button->set_tooltip_text(tooltip);\
	icon->set_padding(0,0);\
	icon->show();	\
	 \
	button->show()
#endif

#define NEW_SMALL_BUTTON(x,y,z)	Gtk::Button *SMALL_BUTTON(x,y,z)

#define NOT_IMPLEMENTED_SLOT sigc::mem_fun(*reinterpret_cast<studio::CanvasViewUIInterface*>(get_ui_interface().get()),&studio::CanvasViewUIInterface::not_implemented)

#define SLOT_EVENT(x)	sigc::hide_return(sigc::bind(sigc::mem_fun(*this,&studio::CanvasView::process_event_key),x))

class studio::CanvasViewUIInterface : public synfigapp::UIInterface
{
    CanvasView *view;

public:

    CanvasViewUIInterface(CanvasView *view):
        view(view)
    {
        view->statusbar->push(_("Idle"));
    }

    ~CanvasViewUIInterface()
    {
    }

    virtual Response confirmation(
        const std::string &message,
        const std::string &details,
        const std::string &cancel,
        const std::string &confirm,
        Response dflt = RESPONSE_OK
    )
    {
        view->present();
        Gtk::MessageDialog dialog(
            *App::main_window,
            message,
            false,
            Gtk::MESSAGE_WARNING,
            Gtk::BUTTONS_NONE,
            true
        );

        if (! details.empty()) {
            dialog.set_secondary_text(details);
        }

        dialog.add_button(cancel, RESPONSE_CANCEL);
        dialog.add_button(confirm, RESPONSE_OK);
        dialog.set_default_response(dflt);

        dialog.show_all();
        return (Response) dialog.run();
    }

    virtual Response yes_no_cancel(
        const std::string &message,
        const std::string &details,
        const std::string &button1,
        const std::string &button2,
        const std::string &button3,
        Response dflt = RESPONSE_YES
    )
    {
        view->present();
        Gtk::MessageDialog dialog(
            *App::main_window,
            message,
            false,
            Gtk::MESSAGE_QUESTION,
            Gtk::BUTTONS_NONE,
            true
        );

        dialog.set_secondary_text(details);
        dialog.add_button(button1, RESPONSE_NO);
        dialog.add_button(button2, RESPONSE_CANCEL);
        dialog.add_button(button3, RESPONSE_YES);

        dialog.set_default_response(dflt);
        dialog.show();
        return (Response)dialog.run();
    }

    virtual bool
    task(const std::string &task)
    {
        if (!view->is_playing_) {
            view->statusbar->pop();
            view->statusbar->push(task);
        }

        if (view->cancel) {
            return false;
        }

        return true;
    }

    virtual bool
    error(const std::string &err)
    {
        view->statusbar->push(_("ERROR"));

        // If we are in the process of canceling,
        // then just go ahead and return false --
        // don't bother displaying a dialog
        if (view->cancel) {
            return false;
        }

        Gtk::MessageDialog dialog(*App::main_window, err, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE, true);
        dialog.show();
        dialog.run();
        view->statusbar->pop();
        return true;
    }

    virtual bool
    warning(const std::string &err)
    {
        view->statusbar->pop();
        view->statusbar->push(err);

        if (view->cancel) {
            return false;
        }

        return true;
    }

    virtual bool
    amount_complete(int current, int total)
    {
        if (!view->is_playing_) {
            if (!view->working_depth) {
                if (current) {
                    view->stopbutton->set_sensitive(true);
                } else {
                    view->stopbutton->set_sensitive(false);
                }
            }

            float x((float)current / (float)total);

            if (x < 0) {
                x = 0;
            } else if (x > 1) {
                x = 1;
            }

            view->progressbar->set_fraction(x);
        }

        if (view->cancel) {
            return false;
        }

        return true;
    }

    void
    not_implemented()
    {
        error(_("Feature not yet implemented"));
    }
};

class studio::CanvasViewSelectionManager : public synfigapp::SelectionManager
{
    CanvasView *view;
    CanvasView::LayerTreeModel layer_tree_model;
    CanvasView::ChildrenTreeModel children_tree_model;

public:
    CanvasViewSelectionManager(CanvasView *view): view(view) { }

private:
    void _set_selected_layer(const synfig::Layer::Handle &layer)
    {
        view->layer_tree->select_layer(layer);

    }
public:

    // Returns the number of layers selected.
    virtual int get_selected_layer_count()const
    {
        return get_selected_layers().size();
    }

    // Returns a list of the currently selected layers.
    virtual LayerList get_selected_layers()const
    {

        if (!view->layer_tree) {
            synfig::error("%s:%d canvas_view.layer_tree not defined!?", __FILE__, __LINE__);
            return LayerList();
        }

        return view->layer_tree->get_selected_layers();
    }

    // Returns the first layer selected or an empty handle if none are selected.
    virtual synfig::Layer::Handle get_selected_layer()const
    {

        if (!view->layer_tree) {
            synfig::error("%s:%d canvas_view.layer_tree not defined!?", __FILE__, __LINE__);
            return 0;
        }

        return view->layer_tree->get_selected_layer();
    }

    // Sets which layers should be selected
    virtual void set_selected_layers(const LayerList &layer_list)
    {

        if (!view->layer_tree) {
            synfig::error("%s:%d canvas_view.layer_tree not defined!?", __FILE__, __LINE__);
            return;
        }

        view->layer_tree->select_layers(layer_list);

    }

    // Sets which layer should be selected.
    virtual void set_selected_layer(const synfig::Layer::Handle &layer)
    {

        if (!view->layer_tree) {
            synfig::error("canvas_view.layer_tree not defined!?");
            return;
        }

        view->layer_tree->select_layer(layer);
    }

    // Clears the layer selection list
    virtual void clear_selected_layers()
    {
        if (!view->layer_tree) {
            return;
        }

        view->layer_tree->clear_selected_layers();
    }

    virtual LayerList get_expanded_layers()const
    {
        if (!view->layer_tree) {
            synfig::error("%s:%d canvas_view.layer_tree not defined!?", __FILE__, __LINE__);
            return LayerList();
        }

        return view->layer_tree->get_expanded_layers();
    }

    virtual void set_expanded_layers(const LayerList &layer_list)
    {
        if (!view->layer_tree) {
            synfig::error("%s:%d canvas_view.layer_tree not defined!?", __FILE__, __LINE__);
            return;
        }

        view->layer_tree->expand_layers(layer_list);
    }

    // Returns the number of value_nodes selected.
    virtual int get_selected_children_count()const
    {
        return get_selected_children().size();
    }

    static inline void __child_grabber(const Gtk::TreeModel::iterator& iter, ChildrenList* ret)
    {
        const CanvasView::ChildrenTreeModel children_tree_model;
        synfigapp::ValueDesc value_desc((*iter)[children_tree_model.value_desc]);

        if (value_desc) {
            ret->push_back(value_desc);
        }
    }

    // Returns a list of the currently selected value_nodes.
    virtual ChildrenList get_selected_children()const
    {
        if (!view->children_tree) {
            return ChildrenList();
        }

        Glib::RefPtr<Gtk::TreeSelection> selection = view->children_tree->get_selection();

        if (!selection) {
            return ChildrenList();
        }

        ChildrenList ret;

        selection->selected_foreach_iter(
            sigc::bind(
                sigc::ptr_fun(
                    &studio::CanvasViewSelectionManager::__child_grabber
                ),
                &ret
            )
        );


        return ret;
    }

    // Returns the first value_node selected or an empty handle if none are selected.
    virtual ChildrenList::value_type get_selected_child()const
    {
        if (!view->children_tree) {
            return ChildrenList::value_type();
        }

        ChildrenList children(get_selected_children());

        if (children.empty()) {
            return ChildrenList::value_type();
        }

        return children.front();
    }

    // Sets which value_nodes should be selected
    virtual void set_selected_children(const ChildrenList &/*children_list*/)
    {
        return;
    }

    // Sets which value_node should be selected. Empty handle if none.
    virtual void set_selected_child(const ChildrenList::value_type &/*child*/)
    {
        return;
    }

    // Clears the value_node selection list
    virtual void clear_selected_children()
    {
        return;
    }

    int get_selected_layer_parameter_count()const
    {
        return get_selected_layer_parameters().size();
    }

    LayerParamList get_selected_layer_parameters()const
    {
        if (!view->layer_tree) {
            return LayerParamList();
        }

        Glib::RefPtr<Gtk::TreeSelection> selection = view->layer_tree->get_selection();

        if (!selection) {
            return LayerParamList();
        }

        LayerParamList ret;

        Gtk::TreeModel::Children children = const_cast<CanvasView*>(view)->layer_tree_store()->children();
        Gtk::TreeModel::Children::iterator iter;

        for (iter = children.begin(); iter != children.end(); ++iter) {
            Gtk::TreeModel::Row row = *iter;
            Gtk::TreeModel::Children::iterator iter;

            for (iter = row.children().begin(); iter != row.children().end(); iter++) {
                Gtk::TreeModel::Row row = *iter;

                if (selection->is_selected(row)) {
                    ret.push_back(LayerParam(row[layer_tree_model.layer], (Glib::ustring)row[layer_tree_model.id]));
                }
            }
        }

        return ret;
    }

    LayerParam get_selected_layer_parameter() const
    {
        if (!view->layer_tree) {
            return LayerParam();
        }

        return get_selected_layer_parameters().front();
    }

    void set_selected_layer_parameters(const LayerParamList &/*layer_param_list*/)
    {
        return;
    }

    void set_selected_layer_param(const LayerParam &/*layer_param*/)
    {
        return;
    }

    void clear_selected_layer_parameters()
    {
        return;
    }

}; // END of class SelectionManager

CanvasView::IsWorking::IsWorking(CanvasView &canvas_view_):
    canvas_view_(canvas_view_)
{
    if (!canvas_view_.working_depth) {
        canvas_view_.stopbutton->set_sensitive(true);
    }

    canvas_view_.working_depth++;
    canvas_view_.cancel = false;
}

CanvasView::IsWorking::~IsWorking()
{
    canvas_view_.working_depth--;

    if (!canvas_view_.working_depth) {
        canvas_view_.stopbutton->set_sensitive(false);
    }
}

CanvasView::IsWorking::operator bool()const
{
    if (canvas_view_.cancel) {
        return false;
    }

    return true;
}

CanvasView::ActivationIndex CanvasView::ActivationIndex::last__;

CanvasView::CanvasView(etl::loose_handle<Instance> instance, etl::handle<synfigapp::CanvasInterface> canvas_interface_):
    Dockable(synfig::GUID().get_string(), _("Canvas View")),
    work_area(),
    activation_index_(true),
    smach_(this),
    instance_(instance),
    canvas_interface_(canvas_interface_),
    context_params_(true),
    time_adjustment_(Gtk::Adjustment::create(0, 0, 25, 0, 0, 0)),
    time_window_adjustment_(new studio::Adjust_Window(0, 0, 25, 0, 0, 0)),
    statusbar(manage(new class Gtk::Statusbar())),
    jackbutton(NULL),
    offset_widget(NULL),
    toggleducksdial(Gtk::IconSize::from_name("synfig-small_icon_16x16")),
    resolutiondial(Gtk::IconSize::from_name("synfig-small_icon_16x16")),
    quality_adjustment_(Gtk::Adjustment::create(8, 1, 10, 1, 1, 0)),

    timeslider(manage(new Widget_Timeslider)),
    widget_kf_list(manage(new Widget_Keyframe_List)),

    ui_interface_(new CanvasViewUIInterface(this)),
    selection_manager_(new CanvasViewSelectionManager(this)),
    is_playing_(false),

    jack_enabled(false),
    jack_actual_enabled(false),
    jack_locks(0),
    jack_enabled_in_preview(false),
#ifdef WITH_JACK
    jack_client(NULL),
    jack_synchronizing(true),
    jack_is_playing(false),
    jack_time(0),
    toggling_jack(false),
#endif

    working_depth(0),
    cancel(false),

    canvas_properties(*App::main_window, canvas_interface_),
    canvas_options(*App::main_window, this),
    render_settings(*App::main_window, canvas_interface_),
    waypoint_dialog(*App::main_window, canvas_interface_->get_canvas()),
    keyframe_dialog(*App::main_window, canvas_interface_),
    preview_dialog(),
    sound_dialog(*App::main_window, canvas_interface_)
{
    layer_tree = 0;
    children_tree = 0;
    duck_refresh_flag = true;
    toggling_ducks_ = false;
    toggling_animate_mode_ = false;
    changing_resolution_ = false;
    updating_quality_ = false;
    toggling_show_grid = false;
    toggling_snap_grid = false;

    disp_audio = new Widget_Sound();

    // Minor hack
    get_canvas()->set_time(0);

    // Set up the UI and Selection managers
    canvas_interface()->set_ui_interface(get_ui_interface());
    canvas_interface()->set_selection_manager(get_selection_manager());
    rebuild_ducks_queued = false;




    // create all allocated stuff for this canvas
    audio = new AudioContainer();

    Gtk::Alignment *space = Gtk::manage(new Gtk::Alignment());
    space->set_size_request(4, 4);
    space->show();

    Gtk::Table *layout_table = manage(new class Gtk::Table(4, 1, false));
    layout_table->attach(*create_work_area(),   0, 1, 2, 3, Gtk::EXPAND | Gtk::FILL, Gtk::EXPAND | Gtk::FILL, 0, 0);
    layout_table->attach(*space, 0, 1, 1, 2, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK | Gtk::FILL, 0, 0);
    init_menus();
    layout_table->attach(*create_display_bar(), 0, 1, 0, 1, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK | Gtk::FILL, 0, 0);
    layout_table->attach(*create_time_bar(),    0, 1, 3, 4, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK | Gtk::FILL, 0, 0);

    update_title();

    layout_table->show();

    Gtk::EventBox *event_box = manage(new Gtk::EventBox());
    event_box->add(*layout_table);
    event_box->show();
    event_box->signal_button_press_event().connect(sigc::mem_fun(*this, &studio::CanvasView::on_button_press_event));

    set_use_scrolled(false);
    add(*event_box);

    smach_.set_default_state(&state_normal);

    canvas_interface()->signal_dirty_preview().connect(sigc::mem_fun(*this, &studio::CanvasView::on_dirty_preview));
    canvas_interface()->signal_mode_changed().connect(sigc::mem_fun(*this, &studio::CanvasView::on_mode_changed));

    canvas_interface()->signal_time_changed().connect(sigc::mem_fun(*this, &studio::CanvasView::on_time_changed));

    canvas_interface()->signal_id_changed().connect(sigc::mem_fun(*this, &studio::CanvasView::on_id_changed));
    canvas_interface()->signal_rend_desc_changed().connect(sigc::mem_fun(*this, &studio::CanvasView::refresh_rend_desc));
    waypoint_dialog.signal_changed().connect(sigc::mem_fun(*this, &studio::CanvasView::on_waypoint_changed));
    waypoint_dialog.signal_delete().connect(sigc::mem_fun(*this, &studio::CanvasView::on_waypoint_delete));

    // MODIFIED TIME ADJUSTMENT STUFF....
    time_window_adjustment()->set_child_adjustment(time_adjustment());
    time_window_adjustment()->signal_value_changed().connect(sigc::mem_fun(*this, &studio::CanvasView::refresh_time_window));
    time_adjustment()->signal_value_changed().connect(sigc::mem_fun(*this, &studio::CanvasView::time_was_changed));

    work_area->signal_layer_selected().connect(sigc::mem_fun(*this, &studio::CanvasView::workarea_layer_selected));
    work_area->signal_input_device_changed().connect(sigc::mem_fun(*this, &studio::CanvasView::on_input_device_changed));
    work_area->signal_meta_data_changed().connect(sigc::mem_fun(*this, &studio::CanvasView::on_meta_data_changed));

    canvas_interface()->signal_canvas_added().connect(
        sigc::hide(
            sigc::mem_fun(*instance, &studio::Instance::refresh_canvas_tree)
        )
    );
    canvas_interface()->signal_canvas_removed().connect(
        sigc::hide(
            sigc::mem_fun(*instance, &studio::Instance::refresh_canvas_tree)
        )
    );

    canvas_interface()->signal_layer_param_changed().connect(
        sigc::hide(
            sigc::hide(
                SLOT_EVENT(EVENT_REFRESH_DUCKS)
            )
        )
    );

    canvas_interface()->signal_keyframe_properties().connect(sigc::mem_fun(*this, &studio::CanvasView::show_keyframe_dialog));

    // MUCH TIME STUFF TAKES PLACE IN HERE
    refresh_rend_desc();
    refresh_time_window();

    /*! TODO:  We shouldn't need to do this at construction --
    **	This should be performed at the first time the window
    ** 	becomes visible.
    */
    work_area->queue_render_preview();

    std::vector<Gtk::TargetEntry> listTargets;
    listTargets.push_back(Gtk::TargetEntry("text/uri-list"));
    listTargets.push_back(Gtk::TargetEntry("text/plain"));
    listTargets.push_back(Gtk::TargetEntry("STRING"));

    drag_dest_set(listTargets);
    signal_drag_data_received().connect(sigc::mem_fun(*this, &studio::CanvasView::on_drop_drag_data_received));



    // load sound info from meta data
    {

        on_audio_file_notify(); // redundant setting of the metadata, but oh well, it's no big deal :)
        on_audio_offset_notify();

        // signal connection - since they are all associated with the canvas view

        // hook in signals for sound options box
        sound_dialog.signal_file_changed().connect(sigc::mem_fun(*this, &CanvasView::on_audio_file_change));
        sound_dialog.signal_offset_changed().connect(sigc::mem_fun(*this, &CanvasView::on_audio_offset_change));

        // attach to the preview when it's visible

        // hook to metadata signals
        get_canvas()->signal_meta_data_changed("audiofile").connect(sigc::mem_fun(*this, &CanvasView::on_audio_file_notify));
        get_canvas()->signal_meta_data_changed("audiooffset").connect(sigc::mem_fun(*this, &CanvasView::on_audio_offset_notify));
    }

    // MORE TIME STUFF
    time_window_adjustment()->set_value(get_canvas()->rend_desc().get_time_start());
    time_window_adjustment()->value_changed();

    refresh_rend_desc();
    hide_tables();

    on_time_changed();
    show();

    instance->canvas_view_list().push_front(this);
    instance->signal_canvas_view_created()(this);

    if (App::jack_is_locked()) {
        jack_lock();
    }

#ifdef WITH_JACK
    jack_dispatcher.connect(sigc::mem_fun(*this, &CanvasView::on_jack_sync));
#endif

    App::dock_manager->register_dockable(*this);
    App::main_window->main_dock_book().add(*this);
    present();
    App::set_selected_canvas_view(this);
}

CanvasView::~CanvasView()
{
#ifdef WITH_JACK
    set_jack_enabled(false);
#endif

    App::dock_manager->unregister_dockable(*this);
    signal_deleted()();

    App::ui_manager()->remove_action_group(action_group);

    // Shut down the smach
    smach_.egress();
    smach_.set_default_state(0);

    // We want to ensure that the UI_Manager and
    // the selection manager get destructed right now.
    ui_interface_.reset();
    selection_manager_.reset();

    // Delete any external widgets
    for (; !ext_widget_book_.empty(); ext_widget_book_.erase(ext_widget_book_.begin())) {
        if (ext_widget_book_.begin()->second) {
            delete ext_widget_book_.begin()->second;
        }
    }

    // delete preview
    audio.reset();

    // don't be calling on_dirty_preview once this object has been deleted;
    // this was causing a crash before
    canvas_interface()->signal_dirty_preview().clear();

    if (getenv("SYNFIG_DEBUG_DESTRUCTORS")) {
        synfig::info("CanvasView::~CanvasView(): Deleted");
    }
}

void CanvasView::save_all()
{
    std::list<etl::handle<Instance> >::iterator iter;

    for (iter = App::instance_list.begin(); iter != App::instance_list.end(); iter++) {
        (*iter)->save();
    }
}

void CanvasView::activate()
{
    activation_index_.activate();
    get_smach().process_event(EVENT_REFRESH_TOOL_OPTIONS);
    App::ui_manager()->insert_action_group(action_group);
    update_title();
    present();
}

void CanvasView::deactivate()
{
    get_smach().process_event(EVENT_YIELD_TOOL_OPTIONS);
    App::ui_manager()->remove_action_group(action_group);
    update_title();
}

void CanvasView::present()
{
    studio::App::set_selected_canvas_view(this);

    Dockable::present();

    // If hided by CanvasView::close_view, time to come back to the show
    if (!get_visible()) {
        show();
    }

    update_title();
}

void CanvasView::jack_lock()
{
    ++jack_locks;
#ifdef WITH_JACK

    if (jack_locks == 1) {
        set_jack_enabled(get_jack_enabled());
    }

#endif
}

void CanvasView::jack_unlock()
{
    --jack_locks;
    assert(jack_locks >= 0);
#ifdef WITH_JACK

    if (jack_locks == 0) {
        set_jack_enabled(get_jack_enabled());
    }

#endif
}

#ifdef WITH_JACK
void CanvasView::set_jack_enabled(bool value)
{
    bool actual_value = value && !jack_is_locked();

    if (jack_actual_enabled != actual_value) {
        jack_actual_enabled = actual_value;

        if (jack_actual_enabled) {
            // initialize jack
            stop();
            jack_client = jack_client_open("synfigstudiocanvas", JackNullOption, 0);
            jack_set_sync_callback(jack_client, jack_sync_callback, this);

            if (jack_activate(jack_client) != 0) {
                jack_client_close(jack_client);
                jack_client = NULL;
                jack_actual_enabled = false;
                // make conditions to update button
                jack_enabled = true;
                value = false;
            }
        } else {
            // deinitialize jack
            jack_deactivate(jack_client);
            jack_client_close(jack_client);
            jack_client = NULL;
        }

        jackbutton->set_sensitive(!jack_is_locked());
    }

    if (jack_enabled != value) {
        jack_enabled = value;

        Gtk::IconSize iconsize = Gtk::IconSize::from_name("synfig-small_icon_16x16");
        Gtk::Image *icon;
        offset_widget = jackdial->get_offsetwidget();

        if (jackbutton->get_active() != jack_enabled) {
            jackbutton->set_active(jack_enabled);
        }

        if (jack_enabled) {
            icon = manage(new Gtk::Image(Gtk::StockID("synfig-jack"), iconsize));
            jackbutton->remove();
            jackbutton->add(*icon);
            jackbutton->set_tooltip_text(_("Disable JACK"));
            icon->set_padding(0, 0);
            icon->show();
            offset_widget->show();
        } else {
            icon = manage(new Gtk::Image(Gtk::StockID("synfig-jack"), iconsize));
            jackbutton->remove();
            jackbutton->add(*icon);
            jackbutton->set_tooltip_text(_("Enable JACK"));
            icon->set_padding(0, 0);
            icon->show();
            offset_widget->hide();
        }
    }
}
#endif

std::list<int>&
CanvasView::get_pixel_sizes()
{
    // prime factors of 64 is 2, 2, 2, 2, 2, 2 - see TILE_SIZE in synfig-core/trunk/src/synfig/target_tile.h
    // also see available low-res renderer engines in synfig::rendering::Renderer::initialize_renderers()
    static int pixel_size_array[] = {2, 4, 8, 16};
    static list<int> pixel_sizes = list<int>(pixel_size_array, pixel_size_array + sizeof(pixel_size_array) / sizeof(int));
    return pixel_sizes;
}

Gtk::Widget *
CanvasView::create_time_bar()
{
    // Setup the Time Slider and the Time window scroll
    Gtk::HScrollbar *time_window_scroll = manage(new class Gtk::HScrollbar(time_window_adjustment()));
    // TIME BAR TEMPORARY POSITION
    // Widget_Timeslider *time_scroll = manage(new Widget_Timeslider);
    timeslider->set_time_adjustment(time_adjustment());
    timeslider->set_bounds_adjustment(time_window_adjustment());
    // Setup the keyframe list widget
    widget_kf_list->set_time_adjustment(time_adjustment());
    widget_kf_list->set_canvas_interface(canvas_interface());
    widget_kf_list->show();

    time_window_scroll->set_tooltip_text(_("Moves the time window"));
    timeslider->set_tooltip_text(_("Changes the current time"));
    time_window_scroll->show();
    timeslider->show();
    // time_window_scroll->set_can_focus(true); // Uncomment this produce bad render of the HScroll
    timeslider->set_can_focus(true);

    timetrack = manage(new class Gtk::VBox());
    timetrack->pack_start(*widget_kf_list);
    timetrack->pack_start(*timeslider);
    timetrack->pack_start(*time_window_scroll);
    timetrack->hide();

    // Interpolation widget
    widget_interpolation = manage(new Widget_Enum());
    widget_interpolation->set_param_desc(
        ParamDesc("interpolation")
        .set_hint("enum")
        .add_enum_value(INTERPOLATION_CLAMPED, "clamped", _("Clamped"))
        .add_enum_value(INTERPOLATION_TCB, "auto", _("TCB"))
        .add_enum_value(INTERPOLATION_CONSTANT, "constant", _("Constant"))
        .add_enum_value(INTERPOLATION_HALT, "ease", _("Ease In/Out"))
        .add_enum_value(INTERPOLATION_LINEAR, "linear", _("Linear"))
    );
    widget_interpolation->set_icon(0, Gtk::Button().render_icon_pixbuf(Gtk::StockID("synfig-interpolation_type_clamped"), Gtk::ICON_SIZE_MENU));
    widget_interpolation->set_icon(1, Gtk::Button().render_icon_pixbuf(Gtk::StockID("synfig-interpolation_type_tcb"), Gtk::ICON_SIZE_MENU));
    widget_interpolation->set_icon(2, Gtk::Button().render_icon_pixbuf(Gtk::StockID("synfig-interpolation_type_const"), Gtk::ICON_SIZE_MENU));
    widget_interpolation->set_icon(3, Gtk::Button().render_icon_pixbuf(Gtk::StockID("synfig-interpolation_type_ease"), Gtk::ICON_SIZE_MENU));
    widget_interpolation->set_icon(4, Gtk::Button().render_icon_pixbuf(Gtk::StockID("synfig-interpolation_type_linear"), Gtk::ICON_SIZE_MENU));
    widget_interpolation->set_tooltip_text(_("Default Interpolation"));
    widget_interpolation->set_popup_fixed_width(false);
    widget_interpolation->set_size_request(120, 0);
    widget_interpolation->show();
    Gtk::Alignment* widget_interpolation_align = manage(new Gtk::Alignment(1, Gtk::ALIGN_CENTER, 0, 0));
    widget_interpolation_align->add(*widget_interpolation);
    widget_interpolation_align->show();
    widget_interpolation_scroll = manage(new class Gtk::ScrolledWindow());
    widget_interpolation_scroll->add(*widget_interpolation_align);
    widget_interpolation_scroll->show();
    widget_interpolation_scroll->set_shadow_type(Gtk::SHADOW_NONE);
    widget_interpolation_scroll->set_policy(Gtk::POLICY_ALWAYS, Gtk::POLICY_NEVER);
    widget_interpolation_scroll->set_size_request(25, 0);
    Gtk::Scrollbar* hscroll = widget_interpolation_scroll->get_hscrollbar();
    hscroll->hide();

    widget_interpolation->signal_changed().connect(sigc::mem_fun(*this, &studio::CanvasView::on_interpolation_changed));
    widget_interpolation_scroll->add_events(Gdk::POINTER_MOTION_MASK);
    widget_interpolation_scroll->signal_event().connect(sigc::bind_return(sigc::mem_fun(*this, &studio::CanvasView::on_interpolation_event), false));

    synfigapp::Main::signal_interpolation_changed().connect(sigc::mem_fun(*this, &studio::CanvasView::interpolation_refresh));
    synfigapp::Main::set_interpolation(INTERPOLATION_CLAMPED); // Clamped by default.
    interpolation_refresh();

    // Setup the Animation Mode Button and the Keyframe Lock button
    {
        Gtk::IconSize iconsize = Gtk::IconSize::from_name("synfig-small_icon_16x16");
        Gtk::Image *icon = manage(new Gtk::Image(Gtk::StockID("synfig-animate_mode_off"), iconsize));
        animatebutton = Gtk::manage(new class Gtk::ToggleButton());
        animatebutton->set_tooltip_text(_("Turn on animate editing mode"));
        icon->set_padding(0, 0);
        icon->show();
        animatebutton->add(*icon);
        animatebutton->signal_toggled().connect(sigc::mem_fun(*this, &studio::CanvasView::toggle_animatebutton));
        animatebutton->set_relief(Gtk::RELIEF_NONE);
        animatebutton->show();
    }

    {
        Gtk::IconSize iconsize = Gtk::IconSize::from_name("synfig-small_icon_16x16");
        Gtk::Image *icon = manage(new Gtk::Image(Gtk::StockID("synfig-timetrack"), iconsize));
        timetrackbutton = Gtk::manage(new class Gtk::ToggleButton());
        timetrackbutton->set_tooltip_text(_("Toggle timebar"));
        icon->set_padding(0, 0);
        icon->show();
        timetrackbutton->add(*icon);
        timetrackbutton->signal_toggled().connect(sigc::mem_fun(*this, &studio::CanvasView::toggle_timetrackbutton));
        timetrackbutton->set_relief(Gtk::RELIEF_NONE);
        timetrackbutton->show();
    }

    // Setup the audio display
    disp_audio->set_size_request(-1, 32);
    disp_audio->set_time_adjustment(time_adjustment());
    disp_audio->signal_start_scrubbing().connect(
        sigc::mem_fun(*audio, &AudioContainer::start_scrubbing)
    );
    disp_audio->signal_scrub().connect(
        sigc::mem_fun(*audio, &AudioContainer::scrub)
    );
    disp_audio->signal_stop_scrubbing().connect(
        sigc::mem_fun(*audio, &AudioContainer::stop_scrubbing)
    );
    // Setup the current time widget
    current_time_widget = manage(new Widget_Time);
    current_time_widget->set_value(get_time());
    current_time_widget->set_fps(get_canvas()->rend_desc().get_frame_rate());
    current_time_widget->signal_value_changed().connect(
        sigc::mem_fun(*this, &CanvasView::on_current_time_widget_changed)
    );
    current_time_widget->set_size_request(0, -1); // request horizontal shrink
    current_time_widget->set_width_chars(5);
    current_time_widget->set_tooltip_text(_("Current time"));
    current_time_widget->show();

    // Setup the FrameDial widget
    framedial = manage(new class FrameDial());
    framedial->signal_seek_begin().connect(
        sigc::bind(sigc::mem_fun(*canvas_interface().get(), &synfigapp::CanvasInterface::seek_time), Time::begin())
    );

    framedial->signal_seek_prev_keyframe().connect(sigc::mem_fun(*canvas_interface().get(), &synfigapp::CanvasInterface::jump_to_prev_keyframe));
    framedial->signal_seek_prev_frame().connect(sigc::bind(sigc::mem_fun(*canvas_interface().get(), &synfigapp::CanvasInterface::seek_frame), -1));
    framedial->signal_play().connect(sigc::mem_fun(*this, &studio::CanvasView::on_play_pause_pressed));
    framedial->signal_pause().connect(sigc::mem_fun(*this, &studio::CanvasView::on_play_pause_pressed));
    framedial->signal_seek_next_frame().connect(sigc::bind(sigc::mem_fun(*canvas_interface().get(), &synfigapp::CanvasInterface::seek_frame), 1));
    framedial->signal_seek_next_keyframe().connect(sigc::mem_fun(*canvas_interface().get(), &synfigapp::CanvasInterface::jump_to_next_keyframe));
    framedial->signal_seek_end().connect(sigc::bind(sigc::mem_fun(*canvas_interface().get(), &synfigapp::CanvasInterface::seek_time), Time::end()));
    framedial->show();

    // Setup the KeyFrameDial widget
    KeyFrameDial *keyframedial = Gtk::manage(new class KeyFrameDial());
    keyframedial->signal_toggle_keyframe_past().connect(sigc::mem_fun(*this, &studio::CanvasView::toggle_past_keyframe_button));
    keyframedial->signal_toggle_keyframe_future().connect(sigc::mem_fun(*this, &studio::CanvasView::toggle_future_keyframe_button));
    keyframedial->show();
    pastkeyframebutton = keyframedial->get_toggle_pastbutton();
    futurekeyframebutton = keyframedial->get_toggle_futurebutton();

    // Adjust both widgets to be the same as the
    int header_height = 0;

    if (getenv("SYNFIG_TIMETRACK_HEADER_HEIGHT")) {
        header_height = atoi(getenv("SYNFIG_TIMETRACK_HEADER_HEIGHT"));
    }

    if (header_height < 3) {
        header_height = 24;
    }

    timeslider->set_size_request(-1, header_height - header_height / 3 + 1);
    widget_kf_list->set_size_request(-1, header_height / 3 + 1);

    Gtk::Alignment *space = Gtk::manage(new Gtk::Alignment());
    space->set_size_request(4);
    space->show();

    Gtk::Alignment *space2 = Gtk::manage(new Gtk::Alignment());
    space2->set_size_request(4);

    jackdial = manage(new class JackDial());
#ifdef WITH_JACK
    jackbutton = jackdial->get_toggle_jackbutton();
    jackdial->signal_toggle_jack().connect(sigc::mem_fun(*this, &studio::CanvasView::toggle_jack_button));
    jackdial->signal_offset_changed().connect(sigc::mem_fun(*this, &studio::CanvasView::on_jack_offset_changed));
    jackdial->set_fps(get_canvas()->rend_desc().get_frame_rate());
    jackdial->set_offset(get_jack_offset());

    if (!getenv("SYNFIG_DISABLE_JACK")) {
        jackdial->show();
        space2->show();
    }

#endif

    // fix thickness of statusbar
    Gtk::Widget *widget = statusbar;

    while (Gtk::Container *container = dynamic_cast<Gtk::Container*>(widget)) {
        widget->set_margin_top(0);
        widget->set_margin_bottom(0);
        widget = container->get_children().empty() ? NULL : container->get_children().front();
    }

    statusbar->show();

    timebar = Gtk::manage(new class Gtk::Table(11, 2, false));

    // Attach widgets to the timebar
    timebar->attach(*timetrackbutton,              0,  1, 1, 2, Gtk::SHRINK | Gtk::FILL, Gtk::SHRINK | Gtk::FILL);
    timebar->attach(*current_time_widget,          1,  2, 1, 2, Gtk::SHRINK | Gtk::FILL, Gtk::SHRINK | Gtk::FILL);
    timebar->attach(*framedial,                    2,  4, 1, 2, Gtk::SHRINK | Gtk::FILL, Gtk::SHRINK | Gtk::FILL);
    timebar->attach(*jackdial,                     4,  5, 1, 2, Gtk::SHRINK | Gtk::FILL, Gtk::SHRINK | Gtk::FILL);
    timebar->attach(*statusbar,                    5,  7, 1, 2, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK | Gtk::FILL);
    timebar->attach(*widget_interpolation_scroll,  7,  8, 1, 2, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK | Gtk::FILL);
    timebar->attach(*keyframedial,                 8,  9, 1, 2, Gtk::SHRINK | Gtk::FILL, Gtk::SHRINK | Gtk::FILL);
    timebar->attach(*space,                        9, 10, 1, 2, Gtk::SHRINK | Gtk::FILL, Gtk::SHRINK | Gtk::FILL);
    timebar->attach(*animatebutton,               10, 11, 1, 2, Gtk::SHRINK | Gtk::FILL, Gtk::SHRINK | Gtk::FILL);

    timebar->attach(*timetrack,                    0, 11, 0, 1, Gtk::SHRINK | Gtk::FILL, Gtk::SHRINK | Gtk::FILL);

    timebar->show();

    return timebar;
}

Gtk::Widget *
CanvasView::create_work_area()
{
    work_area = manage(new studio::WorkArea(canvas_interface_));
    work_area->set_instance(get_instance());
    work_area->set_canvas(get_canvas());
    work_area->set_canvas_view(this);
    work_area->set_progress_callback(get_ui_interface().get());
    work_area->signal_popup_menu().connect(sigc::mem_fun(*this, &studio::CanvasView::popup_main_menu));
    work_area->show();
    return work_area;
}

Gtk::ToolButton*
CanvasView::create_action_toolbutton(const Glib::RefPtr<Gtk::Action> &action)
{
    Gtk::ToolButton *button = Gtk::manage(new Gtk::ToolButton());
    button->set_related_action(action);
    button->show();
    return button;
}

Gtk::SeparatorToolItem*
CanvasView::create_tool_separator()
{
    Gtk::SeparatorToolItem *separator = Gtk::manage(new Gtk::SeparatorToolItem());
    separator->show();
    return separator;
}

Gtk::Widget*
CanvasView::create_display_bar()
{
    Gtk::IconSize iconsize = Gtk::IconSize::from_name("synfig-small_icon_16x16");

    displaybar = manage(new class Gtk::Toolbar());
    displaybar->set_icon_size(iconsize);
    displaybar->set_toolbar_style(Gtk::TOOLBAR_BOTH_HORIZ);
  
    // Setup the ToggleDuckDial widget
    Duck::Type m = work_area->get_type_mask();
    toggleducksdial.update_toggles(m);
    toggleducksdial.signal_ducks_position().connect(
        sigc::bind(sigc::mem_fun(*this, &studio::CanvasView::toggle_duck_mask), Duck::TYPE_POSITION));
    toggleducksdial.signal_ducks_vertex().connect(
        sigc::bind(sigc::mem_fun(*this, &studio::CanvasView::toggle_duck_mask), Duck::TYPE_VERTEX));
    toggleducksdial.signal_ducks_tangent().connect(
        sigc::bind(sigc::mem_fun(*this, &studio::CanvasView::toggle_duck_mask), Duck::TYPE_TANGENT));
    toggleducksdial.signal_ducks_radius().connect(
        sigc::bind(sigc::mem_fun(*this, &studio::CanvasView::toggle_duck_mask), Duck::TYPE_RADIUS));
    toggleducksdial.signal_ducks_width().connect(
        sigc::bind(sigc::mem_fun(*this, &studio::CanvasView::toggle_duck_mask), Duck::TYPE_WIDTH));
    toggleducksdial.signal_ducks_angle().connect(
        sigc::bind(sigc::mem_fun(*this, &studio::CanvasView::toggle_duck_mask), Duck::TYPE_ANGLE));
    toggleducksdial.insert_to_toolbar(*displaybar);

    // Separator
    displaybar->append(*create_tool_separator());

    {
        // Set up the show grid toggle button
        Gtk::Image *icon = manage(new Gtk::Image(Gtk::StockID("synfig-toggle_show_grid"), iconsize));
        icon->set_padding(0, 0);
        icon->show();

        show_grid = Gtk::manage(new class Gtk::ToggleToolButton());
        show_grid->set_active(work_area->grid_status());
        show_grid->set_icon_widget(*icon);
        show_grid->signal_toggled().connect(
            sigc::mem_fun(*this, &studio::CanvasView::toggle_show_grid));
        show_grid->set_label(_("Show grid"));
        show_grid->set_tooltip_text(_("Show grid when enabled"));
        show_grid->show();

        displaybar->append(*show_grid);
    }

    {
        // Set up the snap to grid toggle button
        Gtk::Image *icon = manage(new Gtk::Image(Gtk::StockID("synfig-toggle_snap_grid"), iconsize));
        icon->set_padding(0, 0);
        icon->show();

        snap_grid = Gtk::manage(new class Gtk::ToggleToolButton());
        snap_grid->set_active(work_area->grid_status());
        snap_grid->set_icon_widget(*icon);
        snap_grid->signal_toggled().connect(
            sigc::mem_fun(*this, &studio::CanvasView::toggle_snap_grid));
        snap_grid->set_label(_("Snap to grid"));
        snap_grid->set_tooltip_text(_("Snap to grid when enabled"));
        snap_grid->show();

        displaybar->append(*snap_grid);
    }

    // Separator
    displaybar->append(*create_tool_separator());

    {
        // Setup refresh button
        Gtk::Image *icon = Gtk::manage(new Gtk::Image(Gtk::StockID("gtk-refresh"), iconsize));
        icon->set_padding(0, 0);
        icon->show();

        refreshbutton = Gtk::manage(new class Gtk::ToolButton());
        refreshbutton->set_icon_widget(*icon);
        refreshbutton->signal_clicked().connect(SLOT_EVENT(EVENT_REFRESH));
        refreshbutton->set_label(_("Refresh"));
        refreshbutton->set_tooltip_text(_("Refresh workarea"));
        refreshbutton->show();

        displaybar->append(*refreshbutton);
    }

    // Separator
    displaybar->append(*create_tool_separator());

    // Set up the ResolutionDial widget
    resolutiondial.update_lowres(work_area->get_low_resolution_flag());
    resolutiondial.signal_increase_resolution().connect(
        sigc::mem_fun(*this, &studio::CanvasView::decrease_low_res_pixel_size));
    resolutiondial.signal_decrease_resolution().connect(
        sigc::mem_fun(*this, &studio::CanvasView::increase_low_res_pixel_size));
    resolutiondial.signal_use_low_resolution().connect(
        sigc::mem_fun(*this, &studio::CanvasView::toggle_low_res_pixel_flag));
    resolutiondial.insert_to_toolbar(*displaybar);

    displaybar->show();

    progressbar = manage(new class Gtk::ProgressBar());
    progressbar->show();
    cancel = false;

    {
        Gtk::Image *icon = Gtk::manage(new Gtk::Image(Gtk::StockID("gtk-stop"), iconsize));
        icon->set_padding(0, 0);
        icon->show();

        stopbutton = Gtk::manage(new class Gtk::Button());
        stopbutton->set_image(*icon);
        stopbutton->signal_clicked().connect(SLOT_EVENT(EVENT_STOP));
        stopbutton->set_relief(Gtk::RELIEF_NONE);
        stopbutton->set_tooltip_text(_("Stop current operation"));
        stopbutton->set_sensitive(false);
        stopbutton->show();
    }

    Gtk::HBox *hbox = manage(new class Gtk::HBox(false, 0));
    hbox->pack_start(*displaybar, false, true);
    hbox->pack_end(*stopbutton, false, false);
    hbox->show();

    return hbox;
}

void
CanvasView::on_current_time_widget_changed()
{
    set_time(current_time_widget->get_value());

    // show the value being used - it will have been rounded to nearest frame
    // this was already being done elsewhere, but only if the time was really changed;
    // if the current time was 6f and the user edited it to 6.1f, then the 6.1f would
    // be left in the display without the following line to fix it
    current_time_widget->set_value(get_time());
    current_time_widget->set_position(-1); // leave the cursor at the end
}

void
CanvasView::init_menus()
{
    // cache the position of desired widgets

    /*Menus to worry about:
    - filemenu
    - editmenu
    - layermenu
    - duckmaskmenu
    - mainmenu
    - canvasmenu
    - viewmenu
    */
    action_group = Gtk::ActionGroup::create("canvasview");

    action_group->add(Gtk::Action::create("save", Gtk::StockID("synfig-save")),
                      hide_return(sigc::mem_fun(*get_instance().get(), &studio::Instance::save))
                     );
    action_group->add(Gtk::Action::create("save-as", Gtk::StockID("synfig-save_as"), _("Save As...")),
                      sigc::hide_return(sigc::mem_fun(*get_instance().get(), &studio::Instance::dialog_save_as))
                     );
    action_group->add(Gtk::Action::create("save-all", Gtk::StockID("synfig-save_all"), _("Save All"), _("Save all opened documents")),
                      sigc::ptr_fun(save_all)
                     );
    action_group->add(Gtk::Action::create("revert", Gtk::Stock::REVERT_TO_SAVED),
                      sigc::hide_return(sigc::mem_fun(*get_instance().get(), &studio::Instance::safe_revert))
                     );

    action_group->add(Gtk::Action::create("import", _("Import...")),
                      sigc::hide_return(sigc::mem_fun(*this, &studio::CanvasView::image_import))
                     );
    action_group->add(Gtk::Action::create("render", Gtk::StockID("synfig-render_options"), _("Render...")),
                      sigc::mem_fun0(render_settings, &studio::RenderSettings::present)
                     );
    action_group->add(Gtk::Action::create("preview", Gtk::StockID("synfig-preview_options"), _("Preview...")),
                      sigc::mem_fun(*this, &CanvasView::on_preview_option)
                     );
    action_group->add(Gtk::Action::create("options", _("Options...")),
                      sigc::mem_fun0(canvas_options, &studio::CanvasOptions::present)
                     );
    action_group->add(Gtk::Action::create("close-document", Gtk::StockID("gtk-close"), _("Close Document")),
                      sigc::hide_return(sigc::mem_fun(*this, &studio::CanvasView::close_instance))
                     );
    action_group->add(Gtk::Action::create("quit", Gtk::StockID("gtk-quit"), _("Quit")),
                      sigc::hide_return(sigc::ptr_fun(&studio::App::quit))
                     );

    action_group->add(Gtk::Action::create("select-all-ducks", _("Select All Handles")),
                      sigc::mem_fun(*work_area, &studio::WorkArea::select_all_ducks)
                     );

    action_group->add(Gtk::Action::create("unselect-all-ducks", _("Unselect All Handles")),
                      sigc::mem_fun(*work_area, &studio::WorkArea::unselect_all_ducks)
                     );

    action_group->add(Gtk::Action::create("select-all-layers", _("Select All Layers")),
                      sigc::mem_fun(*this, &CanvasView::on_select_layers)
                     );

    action_group->add(Gtk::Action::create("unselect-all-layers", _("Unselect All Layers")),
                      sigc::mem_fun(*this, &CanvasView::on_unselect_layers)
                     );

    // the stop is not as normal stop but pause. So use "Pause" in UI, including TEXT and
    // icon. the internal code is still using stop.
    action_group->add(Gtk::Action::create("stop", Gtk::StockID("synfig-animate_pause")),
                      SLOT_EVENT(EVENT_STOP)
                     );

    action_group->add(Gtk::Action::create("refresh", Gtk::StockID("gtk-refresh")),
                      SLOT_EVENT(EVENT_REFRESH)
                     );

    action_group->add(Gtk::Action::create("properties", Gtk::StockID("gtk-properties"), _("Properties...")),
                      sigc::mem_fun0(canvas_properties, &studio::CanvasProperties::present)
                     );

    list<synfigapp::PluginManager::plugin> plugin_list = studio::App::plugin_manager.get_list();

    for (list<synfigapp::PluginManager::plugin>::const_iterator p = plugin_list.begin(); p != plugin_list.end(); ++p) {

        synfigapp::PluginManager::plugin plugin = *p;

        action_group->add(Gtk::Action::create(plugin.id, plugin.name),
                          sigc::bind(
                              sigc::mem_fun(*get_instance().get(), &studio::Instance::run_plugin),
                              plugin.path
                          )
                         );
    }

    // Preview Quality Menu
    {
        int i;
        action_group->add(Gtk::RadioAction::create(quality_group, "quality-00", _("Use Parametric Renderer")),
                          sigc::bind(
                              sigc::mem_fun(*work_area, &studio::WorkArea::set_quality),
                              0
                          )
                         );

        for (i = 1; i <= 10; i++) {
            String note;

            if (i == 1) {
                note = _(" (best)");
            }

            if (i == 10) {
                note = _(" (fastest)");
            }

            Glib::RefPtr<Gtk::RadioAction> action(Gtk::RadioAction::create(quality_group, strprintf("quality-%02d", i),
                                                  strprintf(_("Set Quality to %d"), i) + note));

            if (i == 8) {		// default quality
                action->set_active();
                work_area->set_quality(i);
            }

            action_group->add(action,
                              sigc::bind(
                                  sigc::mem_fun(*this, &studio::CanvasView::set_quality),
                                  i
                              )
                             );
        }
    }

    // Low-Res Quality Menu
    {
        int i;

        for (list<int>::iterator iter = CanvasView::get_pixel_sizes().begin(); iter != CanvasView::get_pixel_sizes().end(); iter++) {
            i = *iter;
            Glib::RefPtr<Gtk::RadioAction> action(Gtk::RadioAction::create(low_res_pixel_size_group, strprintf("lowres-pixel-%d", i),
                                                  strprintf(_("Set Low-Res pixel size to %d"), i)));

            if (i == 2) {		// default pixel size
                action->set_active();
                work_area->set_low_res_pixel_size(i);
            }

            action_group->add(action,
                              sigc::bind(
                                  sigc::mem_fun(*work_area, &studio::WorkArea::set_low_res_pixel_size),
                                  i
                              )
                             );
        }

        Glib::RefPtr<Gtk::Action> action;

        action = Gtk::Action::create("decrease-low-res-pixel-size", _("Decrease Low-Res Pixel Size"));
        action_group->add(action, sigc::mem_fun(this, &studio::CanvasView::decrease_low_res_pixel_size));

        action = Gtk::Action::create("increase-low-res-pixel-size",  _("Increase Low-Res Pixel Size"));
        action_group->add(action, sigc::mem_fun(this, &studio::CanvasView::increase_low_res_pixel_size));

    }

    action_group->add(Gtk::Action::create("play", Gtk::Stock::MEDIA_PLAY),
                      sigc::mem_fun(*this, &studio::CanvasView::on_play_pause_pressed)
                     );

    action_group->add(Gtk::Action::create("dialog-flipbook", _("Preview Window")),
                      sigc::mem_fun0(preview_dialog, &studio::Dialog_Preview::present)
                     );
    // Prevent call to preview window before preview option has created the preview window
    {
        Glib::RefPtr< Gtk::Action > action = action_group->get_action("dialog-flipbook");
        action->set_sensitive(false);
    }

    {
        Glib::RefPtr<Gtk::ToggleAction> action;

        grid_show_toggle = Gtk::ToggleAction::create("toggle-grid-show", _("Show Grid"));
        grid_show_toggle->set_active(work_area->grid_status());
        action_group->add(grid_show_toggle, sigc::mem_fun(*this, &studio::CanvasView::toggle_show_grid));

        grid_snap_toggle = Gtk::ToggleAction::create("toggle-grid-snap", _("Snap to Grid"));
        grid_snap_toggle->set_active(work_area->get_grid_snap());
        action_group->add(grid_snap_toggle, sigc::mem_fun(*this, &studio::CanvasView::toggle_snap_grid));

        action = Gtk::ToggleAction::create("toggle-guide-show", _("Show Guides"));
        action->set_active(work_area->get_show_guides());
        action_group->add(action, sigc::mem_fun(*work_area, &studio::WorkArea::toggle_show_guides));

        action = Gtk::ToggleAction::create("toggle-guide-snap", _("Snap to Guides"));
        action->set_active(work_area->get_guide_snap());
        action_group->add(action, sigc::mem_fun(*work_area, &studio::WorkArea::toggle_guide_snap));

        action = Gtk::ToggleAction::create("toggle-low-res", _("Use Low-Res"));
        action->set_active(work_area->get_low_resolution_flag());
        action_group->add(action, sigc::mem_fun(*this, &studio::CanvasView::toggle_low_res_pixel_flag));


    action_group->add(Gtk::Action::create("canvas-zoom-fit", Gtk::StockID("gtk-zoom-fit")),
                      sigc::mem_fun(*work_area, &studio::WorkArea::zoom_fit)
                     );
    action_group->add(Gtk::Action::create("canvas-zoom-100", Gtk::StockID("gtk-zoom-100")),
                      sigc::mem_fun(*work_area, &studio::WorkArea::zoom_norm)
                     );
    }

    {
        Glib::RefPtr<Gtk::Action> action;

        action = Gtk::Action::create("seek-next-frame", Gtk::StockID("synfig-animate_seek_next_frame"));
        action_group->add(action, sigc::bind(sigc::mem_fun(*canvas_interface().get(), &synfigapp::CanvasInterface::seek_frame), 1));
        action = Gtk::Action::create("seek-prev-frame", Gtk::StockID("synfig-animate_seek_prev_frame"));
        action_group->add(action, sigc::bind(sigc::mem_fun(*canvas_interface().get(), &synfigapp::CanvasInterface::seek_frame), -1));

        action = Gtk::Action::create("seek-next-second", Gtk::Stock::GO_FORWARD, _("Seek Forward"), _("Seek Forward"));
        action_group->add(action, sigc::bind(sigc::mem_fun(*canvas_interface().get(), &synfigapp::CanvasInterface::seek_time), Time(1)));
        action = Gtk::Action::create("seek-prev-second", Gtk::Stock::GO_BACK, _("Seek Backward"), _("Seek Backward"));
        action_group->add(action, sigc::bind(sigc::mem_fun(*canvas_interface().get(), &synfigapp::CanvasInterface::seek_time), Time(-1)));

        action = Gtk::Action::create("seek-end", Gtk::StockID("synfig-animate_seek_end"));
        action_group->add(action, sigc::bind(sigc::mem_fun(*canvas_interface().get(), &synfigapp::CanvasInterface::seek_time), Time::end()));

        action = Gtk::Action::create("seek-begin", Gtk::StockID("synfig-animate_seek_begin"));
        action_group->add(action, sigc::bind(sigc::mem_fun(*canvas_interface().get(), &synfigapp::CanvasInterface::seek_time), Time::begin()));

        action = Gtk::Action::create("jump-next-keyframe", Gtk::StockID("synfig-animate_seek_next_keyframe"));
        action_group->add(action, sigc::mem_fun(*canvas_interface().get(), &synfigapp::CanvasInterface::jump_to_next_keyframe));

        action = Gtk::Action::create("jump-prev-keyframe", Gtk::StockID("synfig-animate_seek_prev_keyframe"));
        action_group->add(action, sigc::mem_fun(*canvas_interface().get(), &synfigapp::CanvasInterface::jump_to_prev_keyframe));

        action = Gtk::Action::create("canvas-zoom-in", Gtk::Stock::ZOOM_IN);
        action_group->add(action, sigc::mem_fun(*work_area, &studio::WorkArea::zoom_in));

        action = Gtk::Action::create("canvas-zoom-out", Gtk::Stock::ZOOM_OUT);
        action_group->add(action, sigc::mem_fun(*work_area, &studio::WorkArea::zoom_out));

        action = Gtk::Action::create("time-zoom-in", Gtk::Stock::ZOOM_IN, _("Zoom In on Timeline"));
        action_group->add(action, sigc::mem_fun(*this, &studio::CanvasView::time_zoom_in));

        action = Gtk::Action::create("time-zoom-out", Gtk::Stock::ZOOM_OUT, _("Zoom Out on Timeline"));
        action_group->add(action, sigc::mem_fun(*this, &studio::CanvasView::time_zoom_out));

    }

    {
        Glib::RefPtr<Gtk::ToggleAction> action;
        // toggle none/last visible
        action = Gtk::ToggleAction::create("mask-none-ducks", _("Toggle None/Last visible Handles"));
        action->set_active(false);
        action_group->add(action,  sigc::mem_fun(*this, &CanvasView::toggle_duck_mask_all));

#define DUCK_MASK(lower,upper,string)												\
		action=Gtk::ToggleAction::create("mask-" #lower "-ducks", string);			\
		action->set_active((bool)(work_area->get_type_mask()&Duck::TYPE_##upper));	\
		action_group->add(action,													\
			sigc::bind(																\
				sigc::mem_fun(*this, &studio::CanvasView::toggle_duck_mask),		\
				Duck::TYPE_##upper))

        DUCK_MASK(position, POSITION, _("Show Position Handles"));
        DUCK_MASK(tangent, TANGENT, _("Show Tangent Handles"));
        DUCK_MASK(vertex, VERTEX, _("Show Vertex Handles"));
        DUCK_MASK(radius, RADIUS, _("Show Radius Handles"));
        DUCK_MASK(width, WIDTH, _("Show Width Handles"));
        DUCK_MASK(widthpoint - position, WIDTHPOINT_POSITION, _("Show WidthPoints Position Handles"));
        DUCK_MASK(angle, ANGLE, _("Show Angle Handles"));
        action_mask_bone_setup_ducks = action;
        DUCK_MASK(bone - recursive, BONE_RECURSIVE, _("Show Recursive Scale Bone Handles"));
        action_mask_bone_recursive_ducks = action;

#undef DUCK_MASK

        action_group->add(Gtk::Action::create("mask-bone-ducks", _("Next Bone Handles")),
                          sigc::mem_fun(*this, &CanvasView::mask_bone_ducks));
    }

}

void
CanvasView::on_select_layers()
{
    Canvas::Handle canvas(get_canvas());

    for (CanvasBase::iterator iter = canvas->begin(); iter != canvas->end(); iter++) {
        layer_tree->select_all_children_layers(*iter);
    }
}

void
CanvasView::on_unselect_layers()
{
    layer_tree->clear_selected_layers();
}

void
CanvasView::add_layer(synfig::String x)
{
    Canvas::Handle canvas;

    synfigapp::SelectionManager::LayerList layer_list(get_selection_manager()->get_selected_layers());

    int target_depth(0);

    if (layer_list.empty()) {
        canvas = get_canvas();
    } else {
        canvas = (*layer_list.begin())->get_canvas();
        target_depth = canvas->get_depth(*layer_list.begin());
    }

    Layer::Handle layer(canvas_interface()->add_layer_to(x, canvas, target_depth));

    if (layer) {
        get_selection_manager()->clear_selected_layers();
        get_selection_manager()->set_selected_layer(layer);
    }
}

void
CanvasView::popup_layer_menu(synfig::Layer::Handle layer)
{
    Gtk::Menu* menu(&parammenu);
    std::vector<Widget*> children = menu->get_children();

    for (std::vector<Widget*>::iterator i = children.begin(); i != children.end(); ++i) {
        menu->remove(**i);
    }

    synfigapp::Action::ParamList param_list;
    param_list.add("time", canvas_interface()->get_time());
    param_list.add("canvas", Canvas::Handle(layer->get_canvas()));
    param_list.add("canvas_interface", canvas_interface());
    param_list.add("layer", layer);



    if (etl::handle<Layer_PasteCanvas>::cast_dynamic(layer)) {
        Gtk::MenuItem *item = manage(new Gtk::ImageMenuItem(
                                         *manage(new Gtk::Image(
                                                 Gtk::StockID("synfig-select_all_child_layers"),
                                                 Gtk::ICON_SIZE_MENU)),
                                         _("Select All Children")));
        item->signal_activate().connect(
            sigc::bind(
                sigc::mem_fun(
                    *layer_tree,
                    &studio::LayerTree::select_all_children_layers),
                layer));
        item->show_all();
        menu->append(*item);
    }

    add_actions_to_menu(menu, param_list, synfigapp::Action::CATEGORY_LAYER);
    get_instance()->add_special_layer_actions_to_menu(menu, layer);

    menu->popup(3, gtk_get_current_event_time());
}

void
CanvasView::register_layer_type(synfig::Layer::Book::value_type &/*lyr*/, std::map<synfig::String, Gtk::Menu*>* /*category_map*/)
{

}

void
CanvasView::build_new_layer_menu(Gtk::Menu &/*menu*/)
{

}

void
CanvasView::popup_main_menu()
{
    Gtk::Menu* menu = dynamic_cast<Gtk::Menu*>(App::ui_manager()->get_widget("/menu-main"));

    if (menu) {
        menu->popup(0, gtk_get_current_event_time());
    }
}

void
CanvasView::on_refresh_pressed()
{
    rebuild_tables();
    rebuild_ducks();
    work_area->queue_render_preview();
}

void
CanvasView::workarea_layer_selected(synfig::Layer::Handle layer)
{
    get_selection_manager()->clear_selected_layers();

    if (layer) {
        get_selection_manager()->set_selected_layer(layer);
    }
}

void
CanvasView::refresh_rend_desc()
{
    float current_frame_rate = get_canvas()->rend_desc().get_frame_rate();
    current_time_widget->set_fps(current_frame_rate);

    // "responsive" current time widget width on time format
    int current_time_lenght = current_time_widget->get_value().get_string(current_frame_rate, App::get_time_format()).length();
#define CURRENT_TIME_MIN_LENGHT 6
    current_time_lenght = current_time_lenght < CURRENT_TIME_MIN_LENGHT ? CURRENT_TIME_MIN_LENGHT : current_time_lenght;
    current_time_widget->set_width_chars(current_time_lenght);
#undef CURRENT_TIME_MIN_LENGHT

    jackdial->set_fps(current_frame_rate);
    widget_kf_list->set_fps(current_frame_rate);

    // ????
    if (!get_time().is_equal(time_adjustment()->get_value())) {
        time_adjustment()->set_value(get_time());
        time_adjustment()->value_changed();
    }

    Time length(get_canvas()->rend_desc().get_time_end() - get_canvas()->rend_desc().get_time_start());

    if (length < DEFAULT_TIME_WINDOW_SIZE) {
        time_window_adjustment()->set_page_increment(length);
        time_window_adjustment()->set_page_size(length);
    } else {
        time_window_adjustment()->set_page_increment(DEFAULT_TIME_WINDOW_SIZE);
        time_window_adjustment()->set_page_size(DEFAULT_TIME_WINDOW_SIZE);
    }

    // set the FPS of the timeslider
    timeslider->set_global_fps(current_frame_rate);

    // set the beginning and ending time of the time slider
    Time begin_time = get_canvas()->rend_desc().get_time_start();
    Time end_time = get_canvas()->rend_desc().get_time_end();

    // Setup the time_window adjustment
    time_window_adjustment()->set_lower(begin_time);
    time_window_adjustment()->set_upper(end_time);
    time_window_adjustment()->set_step_increment(synfig::Time(1.0 / current_frame_rate));

    if (length < time_window_adjustment()->get_page_size()) {
        time_window_adjustment()->set_page_increment(length);
        time_window_adjustment()->set_page_size(length);
    }



    time_window_adjustment()->changed(); // only non-value stuff was changed

    // Setup the time adjustment

    // NOTE THESE TWO SHOULD BE CHANGED BY THE changed() CALL ABOVE

    time_adjustment()->set_step_increment(synfig::Time(1.0 / current_frame_rate));
    time_adjustment()->set_page_increment(synfig::Time(1.0));
    time_adjustment()->set_page_size(0);

    time_adjustment()->changed();



    if (begin_time == end_time) {
        hide_timebar();
    } else {
        show_timebar();
    }

    // clamp time to big bounds...
    if (time_window_adjustment()->get_value() < begin_time) {
        time_window_adjustment()->set_value(begin_time);
        time_window_adjustment()->value_changed();
    }

    if (time_window_adjustment()->get_value() + time_window_adjustment()->get_page_size() > end_time) {
        time_window_adjustment()->set_value(end_time - time_window_adjustment()->get_page_size());
        time_window_adjustment()->value_changed();
    }

    if (time_adjustment()->get_value() < begin_time) {
        time_adjustment()->set_value(begin_time);
        time_adjustment()->value_changed();
    }

    if (time_adjustment()->get_value() > end_time) {
        time_adjustment()->set_value(end_time);
        time_adjustment()->value_changed();
    }



    work_area->queue_render_preview();
}

bool
CanvasView::close_view()
{
    // prevent double click
    closebutton->set_sensitive(false);

    if (get_instance()->get_visible_canvases() == 1) {
        close_instance();
    } else {
        hide();
    }

    return false;
}

static bool _close_instance(etl::handle<Instance> instance)
{
    etl::handle<Instance> argh(instance);

    if (instance->safe_close()) {
        synfig::info("closed");
    }

    return false;
}

bool
CanvasView::close_instance()
{
#ifdef SINGLE_THREADED

    if (get_work_area()->get_updating()) {
        get_work_area()->stop_updating(true); // stop and mark as cancelled

        // give the workarea chances to stop updating
        Glib::signal_timeout().connect(
            sigc::mem_fun(*this, &CanvasView::close_instance),
            250);
    } else
#endif
        Glib::signal_timeout().connect(
            sigc::bind(sigc::ptr_fun(_close_instance),
                       (etl::handle<Instance>)get_instance()),
            250);

    return false;
}

handle<CanvasView>
CanvasView::create(etl::loose_handle<Instance> instance, etl::handle<synfig::Canvas> canvas)
{
    etl::handle<studio::CanvasView> view(new CanvasView(instance, instance->synfigapp::Instance::find_canvas_interface(canvas)));
    return view;
}

void
CanvasView::update_title()
{
    bool modified = get_instance()->get_action_count() > 0;
    bool is_root = get_canvas()->is_root();
    string filename = get_instance()->has_real_filename()
                      ? etl::basename(get_instance()->get_file_name()) : "";
    string canvas_name = get_canvas()->get_name();
    string canvas_id = get_canvas()->get_id();
    string &canvas_title = canvas_name.empty() ? canvas_id : canvas_name;

    string title = filename.empty() ? canvas_title
                   : is_root ? filename
                   : filename + " (" + canvas_title + ")";

    if (modified) {
        title = "*" + title;
    }

    if (get_instance()->synfigapp::Instance::in_repository()) {
        title += " (CVS";

        if (get_instance()->synfigapp::Instance::is_modified()) {
            title += _("-MODIFIED");
        }

        if (get_instance()->synfigapp::Instance::is_updated()) {
            title += _("-UPDATED");
        }

        title += ')';
    }

    set_local_name(title);
    App::dock_manager->update_window_titles();
}

void
CanvasView::on_hide()
{
    smach_.egress();
    Dockable::on_hide();
}

Gtk::Widget*
CanvasView::create_tab_label()
{
    Gtk::EventBox* event_box(manage(new Gtk::EventBox()));

    attach_dnd_to(*event_box);

    Glib::ustring text(get_local_name());

    Gtk::HBox* box(manage(new Gtk::HBox()));
    event_box->add(*box);
    box->show();

    Gtk::Label* label(manage(new Gtk::Label(text)));
    box->pack_start(*label, false, true);

    if (this == App::get_selected_canvas_view().get()) {
        Pango::AttrList list;
        Pango::AttrInt attr = Pango::Attribute::create_attr_weight(Pango::WEIGHT_BOLD);
        list.insert(attr);
        label->set_attributes(list);
    }

    label->show();

    closebutton = manage(new Gtk::Button());
    box->pack_end(*closebutton, false, false, 0);
    Gtk::Image* closebutton_image(manage(new Gtk::Image(
            Gtk::StockID("gtk-close"),
            Gtk::IconSize::from_name("synfig-small_icon"))));
    closebutton->add(*closebutton_image);
    closebutton->signal_clicked().connect(
        sigc::hide_return(sigc::mem_fun(*this, &studio::CanvasView::close_view)));
    closebutton->set_relief(Gtk::RELIEF_NONE);
    closebutton->show_all();

    return event_box;
}

bool
CanvasView::on_button_press_event(GdkEventButton * /* event */)
{
    if (this != App::get_selected_canvas_view()) {
        App::set_selected_canvas_view(this);
    }

    return false;
}

bool
CanvasView::on_key_press_event(GdkEventKey* event)
{
    Gtk::Widget* focused_widget = App::main_window->get_focus();

    if (focused_widget && focused_widget_has_priority(focused_widget)) {
        if (focused_widget->event((GdkEvent*)event)) {
            return true;
        }
    } else if (Dockable::on_key_press_event(event)) {
        return true;
    } else if (focused_widget) {
        return focused_widget->event((GdkEvent*)event);
    }

    return false;
}

bool
CanvasView::focused_widget_has_priority(Gtk::Widget * focused)
{
    if (dynamic_cast<Gtk::Entry*>(focused)) {
        return true;
    }

    return false;
}

void
CanvasView::refresh_tables()
{
}

void
CanvasView::rebuild_tables()
{
}

void
CanvasView::build_tables()
{
}

void
CanvasView::on_layer_toggle(synfig::Layer::Handle layer)
{
    synfigapp::Action::Handle action(synfigapp::Action::create("LayerActivate"));
    assert(action);

    if (!action) {
        return;
    }

    action->set_param("canvas", Canvas::Handle(layer->get_canvas()));

    if (!action->set_param("canvas_interface", canvas_interface())) {
        synfig::error("LayerActivate didn't like CanvasInterface...?");
    }

    action->set_param("time", get_time());
    action->set_param("layer", layer);
    action->set_param("new_status", !layer->active());

    assert(action->is_ready());

    canvas_interface()->get_instance()->perform_action(action);
}

void
CanvasView::popup_param_menu(synfigapp::ValueDesc value_desc, float location, bool bezier)
{
    std::vector<Widget*> children = parammenu.get_children();

    for (std::vector<Widget*>::iterator i = children.begin(); i != children.end(); ++i) {
        parammenu.remove(**i);
    }

    get_instance()->make_param_menu(&parammenu, get_canvas(), value_desc, location, bezier);
    parammenu.popup(3, gtk_get_current_event_time());
}

void
CanvasView::add_actions_to_menu(Gtk::Menu *menu, const synfigapp::Action::ParamList &param_list, synfigapp::Action::Category category)const
{
    get_instance()->add_actions_to_menu(menu, param_list, category);
}

bool
CanvasView::on_layer_user_click(int button, Gtk::TreeRow /*row*/, LayerTree::ColumnID /*column_id*/)
{
    switch (button) {
    case 3: {
        Gtk::MenuItem* menu = dynamic_cast<Gtk::MenuItem*>(App::ui_manager()->get_widget("/menu-main/menu-layer"));

        if (menu && menu->get_submenu()) {
            menu->get_submenu()->popup(button, gtk_get_current_event_time());
        }

    }

    return true;

    default:
        break;
    }

    return false;
}

bool
CanvasView::on_children_user_click(int button, Gtk::TreeRow row, ChildrenTree::ColumnID column_id)
{
    switch (button) {
    case 3: {
        if (column_id == COLUMNID_TIME_TRACK) {
            return false;
        }

        if (!(bool)row[children_tree_model.is_canvas]) {
            synfigapp::ValueDesc value_desc = row[children_tree_model.value_desc];

            if (!value_desc) {
                // TODO:  fix properly -- what is the child dialog for?
                synfig::info("preventing child dialog right-click crash");
                return true;
            }

            assert(value_desc);
            popup_param_menu(value_desc);
            return true;
        }
    }

    return true;

    default:
        break;
    }

    return false;
}

bool
CanvasView::on_keyframe_tree_event(GdkEvent *event)
{
    switch (event->type) {
    case GDK_BUTTON_PRESS:
        switch (event->button.button) {
        case 3: {
            return true;
        }
        break;
        }

        break;

    case GDK_MOTION_NOTIFY:
        break;

    case GDK_BUTTON_RELEASE:
        break;

    default:
        break;
    }

    return false;
}

void
CanvasView::refresh_time_window()
{
    // THESE SHOULD AUTOMATICALLY BE TAKEN CARE OF

    time_adjustment()->set_page_increment(1.0); // One second
    time_adjustment()->set_page_size(0);

    if (get_canvas()) {
        time_adjustment()->set_step_increment(1.0 / get_canvas()->rend_desc().get_frame_rate());
    }

    time_adjustment()->changed();

    // NOTE THIS SHOULD HOOK INTO THE CORRECT SIGNALS...
    if (children_tree) {
        children_tree->queue_draw();
    }
}

void
CanvasView::on_time_changed()
{
    Time time(get_time());

    if (!is_time_equal_to_current_frame(soundProcessor.get_position(), 0.5)) {
        soundProcessor.set_position(time);
    }

#ifdef WITH_JACK

    if (jack_enabled && !jack_synchronizing && !is_time_equal_to_current_frame(jack_time - get_jack_offset())) {
        float fps = get_canvas()->rend_desc().get_frame_rate();
        jack_nframes_t sr = jack_get_sample_rate(jack_client);
        jack_nframes_t nframes = ((double)sr * (time + get_jack_offset()).round(fps));
        jack_transport_locate(jack_client, nframes);
    }

#endif

    current_time_widget->set_value(time);

    try {
        get_canvas()->keyframe_list().find(time);
        current_time_widget->override_color(Gdk::RGBA("#FF0000"));
    } catch (...) {
        current_time_widget->override_color(Gdk::RGBA(0));
    }

    if (get_time() != time_adjustment()->get_value()) {
        // Recenters the window, causing it to jump (possibly undesirably... but whatever)
        if (time < time_window_adjustment()->get_value() ||
                time > time_window_adjustment()->get_value() + time_window_adjustment()->get_page_size()) {
            time_window_adjustment()->set_value(
                time - time_window_adjustment()->get_page_size() / 2
            );
        }

        time_adjustment()->set_value(time);
        time_adjustment()->value_changed();

        // Shouldn't these trees just hook into
        // the time changed signal...?
        // YES THEY SHOULD...
        if (layer_tree) {
            layer_tree->queue_draw();
        }

        if (children_tree) {
            children_tree->queue_draw();
        }
    }
}

void
CanvasView::time_zoom_in()
{
    float frame_rate = get_canvas()->rend_desc().get_frame_rate();
    Time min_page_size = 2 / frame_rate;

    time_window_adjustment()->set_page_size(time_window_adjustment()->get_page_size() * 0.75);

    if (time_window_adjustment()->get_page_size() < min_page_size) {
        time_window_adjustment()->set_page_size(min_page_size);
    }

    time_window_adjustment()->set_page_increment(time_window_adjustment()->get_page_size());
    time_window_adjustment()->changed();

    refresh_time_window();
}

void
CanvasView::time_zoom_out()
{
    Time length = (get_canvas()->rend_desc().get_time_end() -
                   get_canvas()->rend_desc().get_time_start());

    time_window_adjustment()->set_page_size(time_window_adjustment()->get_page_size() / 0.75);

    if (time_window_adjustment()->get_page_size() > length) {
        time_window_adjustment()->set_page_size(length);
    }

    time_window_adjustment()->set_page_increment(time_window_adjustment()->get_page_size());
    time_window_adjustment()->changed();

    refresh_time_window();
}

void
CanvasView::time_was_changed()
{
    synfig::Time time((synfig::Time)(double)time_adjustment()->get_value());
    set_time(time);
}

void
CanvasView::on_edited_value(synfigapp::ValueDesc value_desc, synfig::ValueBase new_value)
{
    canvas_interface()->change_value(value_desc, new_value);
}

void
CanvasView::on_id_changed()
{
    update_title();
}

void
CanvasView::on_mode_changed(synfigapp::CanvasInterface::Mode mode)
{
    if (toggling_animate_mode_) {
        return;
    }

    toggling_animate_mode_ = true;
    // If the animate flag was set in mode...
    Gtk::IconSize iconsize = Gtk::IconSize::from_name("synfig-small_icon_16x16");

    if (mode & synfigapp::MODE_ANIMATE) {
        Gtk::Image *icon;
        icon = manage(new Gtk::Image(Gtk::StockID("synfig-animate_mode_on"), iconsize));
        animatebutton->remove();
        animatebutton->add(*icon);
        animatebutton->set_tooltip_text(_("Turn off animate editing mode"));
        icon->set_padding(0, 0);
        icon->show();
        animatebutton->set_active(true);
    } else {
        Gtk::Image *icon;
        icon = manage(new Gtk::Image(Gtk::StockID("synfig-animate_mode_off"), iconsize));
        animatebutton->remove();
        animatebutton->add(*icon);
        animatebutton->set_tooltip_text(_("Turn on animate editing mode"));
        icon->set_padding(0, 0);
        icon->show();
        animatebutton->set_active(false);
    }

    // Keyframe lock icons
    if (mode & synfigapp::MODE_ANIMATE_FUTURE) {
        Gtk::Image *icon;
        icon = manage(new Gtk::Image(Gtk::StockID("synfig-keyframe_lock_future_on"), iconsize));
        futurekeyframebutton->remove();
        futurekeyframebutton->add(*icon);
        futurekeyframebutton->set_tooltip_text(_("Unlock future keyframes"));
        icon->set_padding(0, 0);
        icon->show();
        futurekeyframebutton->set_active(true);
    } else {
        Gtk::Image *icon;
        icon = manage(new Gtk::Image(Gtk::StockID("synfig-keyframe_lock_future_off"), iconsize));
        futurekeyframebutton->remove();
        futurekeyframebutton->add(*icon);
        futurekeyframebutton->set_tooltip_text(_("Lock future keyframes"));
        icon->set_padding(0, 0);
        icon->show();
        futurekeyframebutton->set_active(false);
    }

    if (mode & synfigapp::MODE_ANIMATE_PAST) {
        Gtk::Image *icon;
        icon = manage(new Gtk::Image(Gtk::StockID("synfig-keyframe_lock_past_on"), iconsize));
        pastkeyframebutton->remove();
        pastkeyframebutton->add(*icon);
        pastkeyframebutton->set_tooltip_text(_("Unlock past keyframes"));
        icon->set_padding(0, 0);
        icon->show();
        pastkeyframebutton->set_active(true);
    } else {
        Gtk::Image *icon;
        icon = manage(new Gtk::Image(Gtk::StockID("synfig-keyframe_lock_past_off"), iconsize));
        pastkeyframebutton->remove();
        pastkeyframebutton->add(*icon);
        pastkeyframebutton->set_tooltip_text(_("Lock past  keyframes"));
        icon->set_padding(0, 0);
        icon->show();
        pastkeyframebutton->set_active(false);
    }

    work_area->queue_draw();
    toggling_animate_mode_ = false;
}

void
CanvasView::toggle_animatebutton()
{
    if (toggling_animate_mode_) {
        return;
    }

    if (get_mode()&synfigapp::MODE_ANIMATE) {
        set_mode(get_mode() - synfigapp::MODE_ANIMATE);
    } else {
        set_mode(get_mode() | synfigapp::MODE_ANIMATE);
    }
}

void
CanvasView::toggle_timetrackbutton()
{
    if (timetrackbutton->get_active()) {
        timetrack->set_visible(true);
    } else {
        timetrack->set_visible(false);
    }
}

void
CanvasView::toggle_past_keyframe_button()
{
    if (toggling_animate_mode_) {
        return;
    }

    synfigapp::CanvasInterface::Mode mode(get_mode());

    if ((mode & synfigapp::MODE_ANIMATE_PAST)) {
        set_mode(get_mode() - synfigapp::MODE_ANIMATE_PAST);
    } else {
        set_mode((get_mode() | synfigapp::MODE_ANIMATE_PAST));
    }
}

void
CanvasView::toggle_future_keyframe_button()
{
    if (toggling_animate_mode_) {
        return;
    }

    synfigapp::CanvasInterface::Mode mode(get_mode());

    if ((mode & synfigapp::MODE_ANIMATE_FUTURE)) {
        set_mode(get_mode() - synfigapp::MODE_ANIMATE_FUTURE);
    } else {
        set_mode(get_mode() | synfigapp::MODE_ANIMATE_FUTURE);
    }
}

bool
CanvasView::duck_change_param(const synfig::Point &value, synfig::Layer::Handle layer, synfig::String param_name)
{
    return canvas_interface()->change_value(synfigapp::ValueDesc(layer, param_name), value);
}

void
CanvasView::selected_layer_color_set(synfig::Color color)
{
    synfigapp::SelectionManager::LayerList selected_list(get_selection_manager()->get_selected_layers());
    synfigapp::SelectionManager::LayerList::iterator iter;

    // Create the action group

    Layer::Handle layer;

    for (iter = selected_list.begin(); iter != selected_list.end(); ++iter) {
        if (*iter == layer) {
            continue;
        }

        layer = *iter;
        on_edited_value(synfigapp::ValueDesc(layer, "color"), color);
    }
}

void
CanvasView::queue_rebuild_ducks()
{
    if (rebuild_ducks_queued) {
        queue_rebuild_ducks_connection.disconnect();
    }

    queue_rebuild_ducks_connection = Glib::signal_timeout().connect(
                                         sigc::bind_return(
                                                 sigc::mem_fun(*this, &CanvasView::rebuild_ducks),
                                                 false
                                         ),
                                         50
                                     );

    rebuild_ducks_queued = true;
}

void
CanvasView::rebuild_ducks()
{


    rebuild_ducks_queued = false;
    // queue_rebuild_ducks_connection.disconnect();

    if (work_area->is_dragging()) {
        queue_rebuild_ducks();
        return;
    }

    if (!duck_refresh_flag) {
        duck_refresh_needed = true;
        return;
    }

    bbox = Rect::zero();

    work_area->clear_ducks();
    work_area->clear_curr_transform_stack();
    work_area->set_time(get_time());
    get_canvas()->set_time(get_time());


    // First do the layers...
    do {
        synfigapp::SelectionManager::LayerList selected_list(get_selection_manager()->get_selected_layers());
        std::set<synfig::Layer::Handle> layer_set(selected_list.begin(), selected_list.end());

        synfig::TransformStack transform_stack;

        work_area->add_ducks_layers(get_canvas(), layer_set, this, transform_stack);

    } while (0);

    // Now do the children
    do {
        synfigapp::SelectionManager::ChildrenList selected_list(get_selection_manager()->get_selected_children());
        synfigapp::SelectionManager::ChildrenList::iterator iter;
        synfig::TransformStack transform_stack;

        if (selected_list.empty()) {
            break;
        } else {
            for (iter = selected_list.begin(); iter != selected_list.end(); ++iter) {
                work_area->add_to_ducks(*iter, this, transform_stack);
            }
        }
    } while (0);

    work_area->refresh_selected_ducks();
    work_area->queue_draw_preview();
}

void
CanvasView::decrease_low_res_pixel_size()
{
    if (changing_resolution_) {
        return;
    }

    changing_resolution_ = true;
    list<int> sizes = CanvasView::get_pixel_sizes();
    int pixel_size = work_area->get_low_res_pixel_size();

    for (list<int>::iterator iter = sizes.begin(); iter != sizes.end(); iter++)
        if (*iter == pixel_size) {
            if (iter == sizes.begin())
                // we already have the smallest low-res pixels possible - turn off low-res instead
            {
                work_area->set_low_resolution_flag(false);
            } else {
                iter--;
                Glib::RefPtr<Gtk::Action> action = action_group->get_action(strprintf("lowres-pixel-%d", *iter));
                action->activate(); // to make sure the radiobutton in the menu is updated too
                work_area->set_low_resolution_flag(true);
            }

            break;
        }

    // Update the "toggle-low-res" action
    Glib::RefPtr<Gtk::ToggleAction> action = Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic(action_group->get_action("toggle-low-res"));
    action->set_active(work_area->get_low_resolution_flag());
    // Update toggle low res button
    resolutiondial.update_lowres(work_area->get_low_resolution_flag());
    changing_resolution_ = false;
}

void
CanvasView::increase_low_res_pixel_size()
{
    if (changing_resolution_) {
        return;
    }

    changing_resolution_ = true;
    list<int> sizes = CanvasView::get_pixel_sizes();
    int pixel_size = work_area->get_low_res_pixel_size();

    if (!work_area->get_low_resolution_flag()) {
        // We were using "hi res" so change it to low res.
        work_area->set_low_resolution_flag(true);
        // Update the "toggle-low-res" action
        Glib::RefPtr<Gtk::ToggleAction> action = Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic(action_group->get_action("toggle-low-res"));
        action->set_active(true);
        // Update the toggle low res button
        resolutiondial.update_lowres(true);
        changing_resolution_ = false;
        return;
    }

    for (list<int>::iterator iter = sizes.begin(); iter != sizes.end(); iter++)
        if (*iter == pixel_size) {
            iter++;

            if (iter != sizes.end()) {
                Glib::RefPtr<Gtk::Action> action = action_group->get_action(strprintf("lowres-pixel-%d", *iter));
                action->activate(); // to make sure the radiobutton in the menu is updated too
                work_area->set_low_resolution_flag(true);
            }

            break;
        }

    // Update the "toggle-low-res" action
    Glib::RefPtr<Gtk::ToggleAction> action = Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic(action_group->get_action("toggle-low-res"));
    action->set_active(work_area->get_low_resolution_flag());
    // Update toggle low res button
    resolutiondial.update_lowres(work_area->get_low_resolution_flag());
    changing_resolution_ = false;
}

void
CanvasView::toggle_low_res_pixel_flag()
{
    if (changing_resolution_) {
        return;
    }

    changing_resolution_ = true;
    work_area->toggle_low_resolution_flag();
    // Update the toggle low res button
    resolutiondial.update_lowres(work_area->get_low_resolution_flag());
    // Update the "toggle-low-res" action
    Glib::RefPtr<Gtk::ToggleAction> action = Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic(action_group->get_action("toggle-low-res"));
    action->set_active(work_area->get_low_resolution_flag());
    changing_resolution_ = false;
}

void
CanvasView::update_quality()
{
    if (updating_quality_) {
        return;
    }

    updating_quality_ = true;
    work_area->set_quality((int) quality_spin->get_value());
    // Update Quality Radio actions
    Glib::RefPtr<Gtk::RadioAction> action = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic(
            action_group->get_action(strprintf("quality-%02d", (int) quality_spin->get_value()))
                                            );
    action->set_active();

    updating_quality_ = false;
}

void
CanvasView::set_quality(int x)
{
    if (updating_quality_) {
        return;
    }

    work_area->set_quality(x);

    // quality_spin creation is commented at QUALITY_SPIN_DISABLED in CanvasView::create_display_bar
    // Update the quality spin button
}

void
CanvasView::toggle_show_grid()
{
    if (toggling_show_grid) {
        return;
    }

    toggling_show_grid = true;
    work_area->toggle_grid();
    // Update the toggle grid show action
    set_grid_show_toggle(work_area->grid_status());
    // Update the toggle grid show check button
    show_grid->set_active(work_area->grid_status());
    toggling_show_grid = false;
}

void
CanvasView::toggle_snap_grid()
{
    if (toggling_snap_grid) {
        return;
    }

    toggling_snap_grid = true;
    work_area->toggle_grid_snap();
    // Update the toggle grid snap action
    set_grid_snap_toggle(work_area->get_grid_snap());
    // Update the toggle grid snap check button
    snap_grid->set_active(work_area->get_grid_snap());
    toggling_snap_grid = false;
}

void
CanvasView::on_dirty_preview()
{
    if (!is_playing_) {
        IsWorking is_working(*this);

        work_area->queue_render_preview();
    }
}

void
CanvasView::play_async()
{
    if (is_playing()) {
        return;
    }

    playing_timer.reset();
    playing_time = work_area->get_time();

    // If we are already at the end of time, start over
    if (playing_time == get_canvas()->rend_desc().get_time_end()) {
        playing_time = get_canvas()->rend_desc().get_time_start();
    }

    is_playing_ = true;

    work_area->clear_ducks();

    float fps = get_canvas()->rend_desc().get_frame_rate();
    int timeout = fps <= 0.f ? 0 : (int)roundf(500.f / fps);

    if (timeout < 10) {
        timeout = 10;
    }

    framedial->toggle_play_pause_button(!is_playing());

    soundProcessor.clear();
    canvas_interface()->get_canvas()->fill_sound_processor(soundProcessor);
    soundProcessor.set_position(canvas_interface()->get_canvas()->get_time());
    soundProcessor.set_playing(true);

    playing_connection = Glib::signal_timeout().connect(
                             sigc::bind_return(sigc::mem_fun(*this, &studio::CanvasView::on_play_timeout), true),
                             timeout,
                             Glib::PRIORITY_LOW);
}

void
CanvasView::stop_async()
{
    playing_connection.disconnect();
    soundProcessor.set_playing(false);
    is_playing_ = false;
    framedial->toggle_play_pause_button(!is_playing());
}

void
CanvasView::on_play_timeout()
{
    Time time;
    // Used ifdef WITH_JACK
    Time starttime = get_canvas()->rend_desc().get_time_start();
    Time endtime = get_canvas()->rend_desc().get_time_end();

    if (jack_enabled) {
#ifdef WITH_JACK
        jack_position_t pos;
        jack_transport_query(jack_client, &pos);
        jack_time = Time((Time::value_type)pos.frame / (Time::value_type)pos.frame_rate);
        time = jack_time - get_jack_offset();

        if (time > endtime) {
            time = endtime;
        }

        if (time < starttime) {
            time = starttime;
        }

#endif
    } else {
        time = playing_time + playing_timer();

        if (time >= endtime) {
            time_adjustment()->set_value(endtime);
            time_adjustment()->value_changed();
            stop_async();
            return;
        }
    }

    // Clamp the time window so we can see the time value as it races across the horizon
    bool timewindreset = false;

    while (time > Time(time_window_adjustment()->get_sub_upper())) {
        time_window_adjustment()->set_value(
            min(
                time_window_adjustment()->get_value() + time_window_adjustment()->get_page_size() / 2,
                time_window_adjustment()->get_upper() - time_window_adjustment()->get_page_size())
        );
        timewindreset = true;
    }

    while (time < Time(time_window_adjustment()->get_sub_lower())) {
        time_window_adjustment()->set_value(
            max(
                time_window_adjustment()->get_value() - time_window_adjustment()->get_page_size() / 2,
                time_window_adjustment()->get_lower())
        );

        timewindreset = true;
    }

    // we need to tell people that the value changed
    if (timewindreset) {
        time_window_adjustment()->value_changed();
    }

    // update actual time to next step
    time_adjustment()->set_value(time);
    time_adjustment()->value_changed();

    if (!work_area->sync_render_preview()) {
        stop_async();
    }
}

void
CanvasView::play()
{
    assert(get_canvas());

    // If we are already busy, don't play!
    if (working_depth) {
        return;
    }

    // Set us up as working
    IsWorking is_working(*this);

    etl::clock timer;
    Time
    time = work_area->get_time(),
    endtime = get_canvas()->rend_desc().get_time_end();

    // If we are already at the end of time, start over
    if (time == endtime) {
        time = get_canvas()->rend_desc().get_time_start();
    }

    is_playing_ = true;

    work_area->clear_ducks();

    for (timer.reset(); time + timer() < endtime;) {
        // Clamp the time window so we can see the time value as it races across the horizon
        bool timewindreset = false;

        while (time + timer() > Time(time_window_adjustment()->get_sub_upper())) {
            time_window_adjustment()->set_value(
                min(
                    time_window_adjustment()->get_value() + time_window_adjustment()->get_page_size() / 2,
                    time_window_adjustment()->get_upper() - time_window_adjustment()->get_page_size())
            );
            timewindreset = true;
        }

        while (time + timer() < Time(time_window_adjustment()->get_sub_lower())) {
            time_window_adjustment()->set_value(
                max(
                    time_window_adjustment()->get_value() - time_window_adjustment()->get_page_size() / 2,
                    time_window_adjustment()->get_lower())
            );

            timewindreset = true;
        }

        // we need to tell people that the value changed
        if (timewindreset) {
            time_window_adjustment()->value_changed();
        }

        // update actual time to next step
        time_adjustment()->set_value(time + timer());
        time_adjustment()->value_changed();

        if (!work_area->sync_render_preview()) {
            break;
        }

        // wait for the workarea to refresh itself
        while (studio::App::events_pending()) {
            studio::App::iteration(false);
        }

        if (get_cancel_status()) {
            is_playing_ = false;
            return;
        }
    }

    is_playing_ = false;
    time_adjustment()->set_value(endtime);
    time_adjustment()->value_changed();
}

void
CanvasView::show_tables()
{

}

void
CanvasView::hide_tables()
{

}

bool
CanvasView::tables_are_visible()
{
    return false;
}

void
CanvasView::toggle_tables()
{
//	else
}

void
CanvasView::show_timebar()
{
    timebar->show();
    // current_time_widget->show(); // not needed now that belongs to the timebar

    if (layer_tree) {
        layer_tree->set_show_timetrack(true);
    }

    if (children_tree) {
        children_tree->set_show_timetrack(true);
    }
}

void
CanvasView::hide_timebar()
{
    timebar->hide();

    // current_time_widget->hide(); // not needed now that belongs to the timebar
    if (layer_tree) {
        layer_tree->set_show_timetrack(false);
    }

    if (children_tree) {
        children_tree->set_show_timetrack(false);
    }
}

void
CanvasView::set_sensitive_timebar(bool sensitive)
{
    timebar->set_sensitive(sensitive);

    // current_time_widget->set_sensitive(sensitive); // not needed now that belongs to timebar
    if (layer_tree) {
        layer_tree->set_sensitive(sensitive);
    }

    if (children_tree) {
        children_tree->set_sensitive(sensitive);
    }
}

static void
set_waypoint_model(std::set<synfig::Waypoint, std::less<UniqueID> > waypoints,
                   Waypoint::Model model,
                   etl::loose_handle<synfigapp::CanvasInterface> canvas_interface)
{
    // Create the action group
    synfigapp::Action::PassiveGrouper group(canvas_interface->get_instance().get(), _("Change Waypoint Group"));

    std::set<synfig::Waypoint, std::less<UniqueID> >::const_iterator iter;

    for (iter = waypoints.begin(); iter != waypoints.end(); ++iter) {
        Waypoint waypoint(*iter);
        waypoint.apply_model(model);

        synfigapp::Action::Handle action(synfigapp::Action::create("WaypointSet"));

        assert(action);

        action->set_param("canvas", canvas_interface->get_canvas());
        action->set_param("canvas_interface", canvas_interface);

        action->set_param("waypoint", waypoint);
        action->set_param("value_node", waypoint.get_parent_value_node());

        if (!canvas_interface->get_instance()->perform_action(action)) {
            group.cancel();
            return;
        }
    }
}

static void
duplicate_waypoints(std::set<synfig::Waypoint, std::less<UniqueID> > waypoints,
                    etl::loose_handle<synfigapp::CanvasInterface> canvas_interface)
{
    // Create the action group
    synfigapp::Action::PassiveGrouper group(canvas_interface->get_instance().get(), _("Duplicate Waypoints"));

    std::set<synfig::Waypoint, std::less<UniqueID> >::const_iterator iter;

    for (iter = waypoints.begin(); iter != waypoints.end(); iter++) {
        Waypoint waypoint(*iter);
        ValueNode::Handle value_node(iter->get_parent_value_node());
        canvas_interface->waypoint_duplicate(value_node, waypoint);
    }
}

static void
remove_waypoints(std::set<synfig::Waypoint, std::less<UniqueID> > waypoints,
                 etl::loose_handle<synfigapp::CanvasInterface> canvas_interface)
{
    // Create the action group
    synfigapp::Action::PassiveGrouper group(canvas_interface->get_instance().get(), _("Remove Waypoints"));

    std::set<synfig::Waypoint, std::less<UniqueID> >::const_iterator iter;

    for (iter = waypoints.begin(); iter != waypoints.end(); iter++) {
        Waypoint waypoint(*iter);
        ValueNode::Handle value_node(iter->get_parent_value_node());
        canvas_interface->waypoint_remove(value_node, waypoint);
    }
}

void
CanvasView::on_waypoint_clicked_canvasview(synfigapp::ValueDesc value_desc,
        std::set<synfig::Waypoint, std::less<UniqueID> > waypoint_set,
        int button)
{
    int size = waypoint_set.size();
    Waypoint waypoint(*(waypoint_set.begin()));
    Time time(waypoint.get_time());

    if (size == 1) {
        waypoint_dialog.set_value_desc(value_desc);
        waypoint_dialog.set_waypoint(waypoint);
    }

    switch (button) {
    case -1:
        if (size == 1) {
            waypoint_dialog.show();
        }

        break;

    case 2: {
        Gtk::Menu* waypoint_menu(manage(new Gtk::Menu()));
        waypoint_menu->signal_hide().connect(sigc::bind(sigc::ptr_fun(&delete_widget), waypoint_menu));

        Gtk::Menu* interp_menu_in(manage(new Gtk::Menu()));
        Gtk::Menu* interp_menu_out(manage(new Gtk::Menu()));
        Gtk::Menu* interp_menu_both(manage(new Gtk::Menu()));
        Gtk::MenuItem *item = NULL;

        {
            Waypoint::Model model;

#define APPEND_MENU_ITEM(menu, StockId, Text) \
item = manage(new Gtk::ImageMenuItem( \
	*manage(new Gtk::Image(Gtk::StockID(StockId),Gtk::IconSize::from_name("synfig-small_icon"))), \
	_(Text) )); \
item->set_use_underline(true); \
item->signal_activate().connect( \
	sigc::bind(sigc::ptr_fun(set_waypoint_model), waypoint_set, model, canvas_interface())); \
item->show_all(); \
menu->append(*item);

#define APPEND_ITEMS_TO_ALL_MENUS3(Interpolation, StockId, TextIn, TextOut, TextBoth) \
model.reset(); \
model.set_before(Interpolation); \
APPEND_MENU_ITEM(interp_menu_in, StockId, TextIn) \
model.reset(); \
model.set_after(Interpolation); \
APPEND_MENU_ITEM(interp_menu_out, StockId, TextOut) \
model.set_before(Interpolation); \
APPEND_MENU_ITEM(interp_menu_both, StockId, TextBoth)

#define APPEND_ITEMS_TO_ALL_MENUS(Interpolation, StockId, Text) \
APPEND_ITEMS_TO_ALL_MENUS3(Interpolation, StockId, Text, Text, Text)

            APPEND_ITEMS_TO_ALL_MENUS(INTERPOLATION_TCB, "synfig-interpolation_type_tcb", _("_TCB"))
            APPEND_ITEMS_TO_ALL_MENUS(INTERPOLATION_LINEAR, "synfig-interpolation_type_linear", _("_Linear"))
            APPEND_ITEMS_TO_ALL_MENUS3(INTERPOLATION_HALT, "synfig-interpolation_type_ease", _("_Ease In"), _("_Ease Out"), _("_Ease In/Out"))
            APPEND_ITEMS_TO_ALL_MENUS(INTERPOLATION_CONSTANT, "synfig-interpolation_type_const", _("_Constant"))
            APPEND_ITEMS_TO_ALL_MENUS(INTERPOLATION_CLAMPED, "synfig-interpolation_type_clamped", _("_Clamped"))

#undef APPEND_ITEMS_TO_ALL_MENUS
#undef APPEND_ITEMS_TO_ALL_MENUS3
#undef APPEND_MENU_ITEM
        }

        if (size == 1) {
            const synfigapp::ValueDesc value_desc(synfig::ValueNode_Animated::Handle::cast_reinterpret(waypoint.get_parent_value_node()), time);
            get_instance()->make_param_menu(waypoint_menu, canvas_interface()->get_canvas(), value_desc, 0.5f);

            item = manage(new Gtk::SeparatorMenuItem());
            item->show();
            waypoint_menu->append(*item);
        }

        item = manage(new Gtk::MenuItem(_("_Jump To")));
        item->set_use_underline(true);
        item->signal_activate().connect(
            sigc::bind(sigc::mem_fun(*canvas_interface(), &synfigapp::CanvasInterface::set_time), time));
        item->show();
        waypoint_menu->append(*item);

        item = manage(new Gtk::MenuItem(_("_Duplicate")));
        item->set_use_underline(true);
        item->signal_activate().connect(
            sigc::bind(sigc::ptr_fun(duplicate_waypoints), waypoint_set, canvas_interface()));
        item->show();
        waypoint_menu->append(*item);

        item = manage(new Gtk::MenuItem(size == 1 ? _("_Remove") : strprintf(_("_Remove %d Waypoints"), size)));
        item->set_use_underline(true);
        item->signal_activate().connect(
            sigc::bind(sigc::ptr_fun(remove_waypoints), waypoint_set, canvas_interface()));
        item->show();
        waypoint_menu->append(*item);

        if (size == 1 && value_desc.is_valid()) {
            item = manage(new Gtk::MenuItem(_("_Edit")));
            item->set_use_underline(true);
            item->signal_activate().connect(
                sigc::mem_fun(waypoint_dialog, &Gtk::Widget::show));
            item->show();
            waypoint_menu->append(*item);
        }

        item = manage(new Gtk::SeparatorMenuItem());
        item->show();
        waypoint_menu->append(*item);

        item = manage(new Gtk::MenuItem(_("_Both")));
        item->set_use_underline(true);
        item->set_submenu(*interp_menu_both);
        item->show();
        waypoint_menu->append(*item);

        item = manage(new Gtk::MenuItem(_("_In")));
        item->set_use_underline(true);
        item->set_submenu(*interp_menu_in);
        item->show();
        waypoint_menu->append(*item);

        item = manage(new Gtk::MenuItem(_("_Out")));
        item->set_use_underline(true);
        item->set_submenu(*interp_menu_out);
        item->show();
        waypoint_menu->append(*item);

        waypoint_menu->popup(button + 1, gtk_get_current_event_time());
    }
    break;

    default:
        break;
    }
}

void
CanvasView::on_waypoint_changed()
{
    synfigapp::Action::ParamList param_list;
    param_list.add("canvas", get_canvas());
    param_list.add("canvas_interface", canvas_interface());
    param_list.add("value_node", waypoint_dialog.get_value_desc().get_value_node());
    param_list.add("waypoint", waypoint_dialog.get_waypoint());

    get_instance()->process_action("WaypointSetSmart", param_list);
}

void
CanvasView::on_waypoint_delete()
{
    synfigapp::Action::ParamList param_list;
    param_list.add("canvas", get_canvas());
    param_list.add("canvas_interface", canvas_interface());
    param_list.add("value_node", waypoint_dialog.get_value_desc().get_value_node());
    param_list.add("waypoint", waypoint_dialog.get_waypoint());

    get_instance()->process_action("WaypointRemove", param_list);
}

void
CanvasView::on_drop_drag_data_received(const Glib::RefPtr<Gdk::DragContext>& context, int /*x*/, int /*y*/, const Gtk::SelectionData& selection_data_, guint /*info*/, guint time)
{
    // We will make this true once we have a solid drop
    bool success(false);

    if ((selection_data_.get_length() >= 0) && (selection_data_.get_format() == 8)) {
        if (synfig::String(selection_data_.get_data_type()) == "STRING"
                || synfig::String(selection_data_.get_data_type()) == "text/plain")do {
                synfig::String selection_data((gchar *)(selection_data_.get_data()));

                Layer::Handle layer(synfig::Layer::create("Text"));

                if (!layer) {
                    break;
                }

                if (!layer->set_param("text", ValueBase(selection_data))) {
                    break;
                }

                synfigapp::Action::Handle 	action(synfigapp::Action::create("LayerAdd"));

                assert(action);

                if (!action) {
                    break;
                }

                action->set_param("canvas", get_canvas());
                action->set_param("canvas_interface", canvas_interface());
                action->set_param("new", layer);

                if (!get_instance()->perform_action(action)) {
                    break;
                }

                // Ok, we have successfully imported at least one item.
                success = true;
            } while (0); // END of "STRING"

        if (synfig::String(selection_data_.get_data_type()) == "text/uri-list") {
            synfig::String selection_data((gchar *)(selection_data_.get_data()));

            // For some reason, GTK hands us a list of URLs separated
            // by not only Carriage-Returns, but also Line-Feeds.
            // Line-Feeds will mess us up. Remove all the line-feeds.
            while (selection_data.find_first_of('\r') != synfig::String::npos) {
                selection_data.erase(selection_data.begin() + selection_data.find_first_of('\r'));
            }

            std::stringstream stream(selection_data);

            while (stream) {
                synfig::String URI;
                getline(stream, URI);

                // If we don't have an URI, move on.
                if (URI.empty()) {
                    continue;
                }

                // Extract protocol name from URI.
                synfig::String protocol(Glib::uri_parse_scheme(URI));

                if (protocol.empty()) {
                    synfig::warning("Cannot extract protocol from URI \"%s\"", URI.c_str());
                    continue;
                }

                // Only 'file' protocol supported
                if (protocol != "file") {
                    synfig::warning("Protocol \"%s\" is unsupported (URI \"%s\")", protocol.c_str(), URI.c_str());
                    continue;
                }

                // Converts an escaped UTF-8 encoded URI to a local filename
                // in the encoding used for filenames.
                synfig::String filename(Glib::filename_from_uri(URI));

                if (filename.empty()) {
                    synfig::warning("Cannot extract filename from URI \"%s\"", URI.c_str());
                    continue;
                }

                String ext(filename_extension(filename));

                if (!ext.empty()) {
                    ext = ext.substr(1);    // skip initial '.'
                }

                // If this is a SIF file, then we need to do things slightly differently
                if (ext == "sketch") {
                    if (work_area->load_sketch(filename)) {
                        success = true;
                        work_area->queue_draw();
                    }
                } else {
                    String errors, warnings;

                    if (canvas_interface()->import(filename, errors, warnings, App::resize_imported_images)) {
                        success = true;
                    }

                    if (warnings != "")
                        App::dialog_message_1b(
                            "WARNING",
                            strprintf("%s:\n\n%s", _("Warning"), warnings.c_str()),
                            "details",
                            _("Close"));
                }
            }
        } // END of "text/uri-list"
    } else {
        ui_interface_->error("Drop failed: bad selection data");
    }

    // Finish the drag
    context->drag_finish(success, false, time);
}

void
CanvasView::on_keyframe_add_pressed()
{
    synfigapp::Action::Handle action(synfigapp::Action::create("KeyframeAdd"));

    if (!action) {
        ui_interface_->error("I am unable to find the appropriate action");
        return;
    }

    action->set_param("canvas", get_canvas());
    action->set_param("canvas_interface", canvas_interface());
    action->set_param("keyframe", Keyframe(get_time()));

    canvas_interface()->get_instance()->perform_action(action);
}

void
CanvasView::on_keyframe_duplicate_pressed()
{
    const KeyframeTreeStore::Model model;
    const Gtk::TreeRow row(*keyframe_tree->get_selection()->get_selected());
    Keyframe keyframe;

    if (!row) {
        ui_interface_->error("I am unable to duplicate the keyframe");
        return;
    }

    keyframe = row[model.keyframe];

    synfigapp::Action::Handle action(synfigapp::Action::create("KeyframeDuplicate"));

    if (!action) {
        ui_interface_->error("I am unable to find the appropriate action");
        return;
    }

    action->set_param("canvas", get_canvas());
    action->set_param("canvas_interface", canvas_interface());
    action->set_param("keyframe", keyframe);
    action->set_param("time", get_time());

    canvas_interface()->get_instance()->perform_action(action);
}

void
CanvasView::on_keyframe_remove_pressed()
{
    const KeyframeTreeStore::Model model;
    const Gtk::TreeRow row(*keyframe_tree->get_selection()->get_selected());
    Keyframe keyframe;

    if (!row) {
        ui_interface_->error("I am unable to remove the keyframe");
        return;
    }

    keyframe = row[model.keyframe];

    synfigapp::Action::Handle action(synfigapp::Action::create("KeyframeRemove"));

    if (!action) {
        ui_interface_->error("I am unable to find the appropriate action");
        return;
    }

    action->set_param("canvas", get_canvas());
    action->set_param("canvas_interface", canvas_interface());
    action->set_param("keyframe", keyframe);

    canvas_interface()->get_instance()->perform_action(action);
}

void
CanvasView::show_keyframe_dialog()
{
    Glib::RefPtr<Gtk::TreeSelection> selection(keyframe_tree->get_selection());

    if (selection->get_selected()) {
        Gtk::TreeRow row(*selection->get_selected());

        Keyframe keyframe(row[keyframe_tree->model.keyframe]);

        keyframe_dialog.set_keyframe(keyframe);
        keyframe_dialog.present();
    }
}

void
CanvasView::on_keyframe_toggle()
{
    Glib::RefPtr<Gtk::TreeSelection> selection(keyframe_tree->get_selection());

    if (selection->get_selected()) {
        Gtk::TreeRow row(*selection->get_selected());

        Keyframe keyframe(row[keyframe_tree->model.keyframe]);

        synfigapp::Action::Handle action(synfigapp::Action::create("KeyframeToggl"));

        if (!action) {
            return;
        }

        action->set_param("canvas", canvas_interface()->get_canvas());
        action->set_param("canvas_interface", canvas_interface());
        action->set_param("keyframe", keyframe);
        action->set_param("new_status", !keyframe.active());

        canvas_interface()->get_instance()->perform_action(action);

    }
}

void
CanvasView::on_keyframe_description_set()
{
    Glib::RefPtr<Gtk::TreeSelection> selection(keyframe_tree->get_selection());

    if (selection->get_selected()) {
        Gtk::TreeRow row(*selection->get_selected());

        Keyframe keyframe(row[keyframe_tree->model.keyframe]);

        synfigapp::Action::Handle action(synfigapp::Action::create("KeyframeSet"));

        if (!action) {
            return;
        }

        String str(keyframe.get_description());

        if (!studio::App::dialog_entry((action->get_local_name() + _(" Description")),
                                       _("Description: "),
                                       str,
                                       _("Cancel"),
                                       _("Set"))) {
            return;
        }

        keyframe.set_description(str);

        action->set_param("canvas", canvas_interface()->get_canvas());
        action->set_param("canvas_interface", canvas_interface());
        action->set_param("keyframe", keyframe);

        canvas_interface()->get_instance()->perform_action(action);

    }
}

void
CanvasView::toggle_duck_mask(Duckmatic::Type type)
{
    if (toggling_ducks_) {
        return;
    }

    toggling_ducks_ = true;

    if (type & Duck::TYPE_WIDTH) {
        type = type | Duck::TYPE_WIDTHPOINT_POSITION;
    }

    bool is_currently_on(work_area->get_type_mask()&type);

    if (is_currently_on) {
        work_area->set_type_mask(work_area->get_type_mask() - type);
    } else {
        work_area->set_type_mask(work_area->get_type_mask() | type);
    }

    if (type == Duck::TYPE_BONE_RECURSIVE) {
        queue_rebuild_ducks();
    }

    work_area->queue_draw();

    try {
        // Update the toggle ducks actions
        Glib::RefPtr<Gtk::ToggleAction> action;
        action = Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic(action_group->get_action("mask-position-ducks"));
        action->set_active((bool)(work_area->get_type_mask()&Duck::TYPE_POSITION));
        action = Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic(action_group->get_action("mask-tangent-ducks"));
        action->set_active((bool)(work_area->get_type_mask()&Duck::TYPE_TANGENT));
        action = Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic(action_group->get_action("mask-vertex-ducks"));
        action->set_active((bool)(work_area->get_type_mask()&Duck::TYPE_VERTEX));
        action = Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic(action_group->get_action("mask-radius-ducks"));
        action->set_active((bool)(work_area->get_type_mask()&Duck::TYPE_RADIUS));
        action = Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic(action_group->get_action("mask-width-ducks"));
        action->set_active((bool)(work_area->get_type_mask()&Duck::TYPE_WIDTH));
        action = Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic(action_group->get_action("mask-angle-ducks"));
        action->set_active((bool)(work_area->get_type_mask()&Duck::TYPE_ANGLE));
        // Update toggle ducks buttons
        action->get_active();
        toggleducksdial.update_toggles(work_area->get_type_mask());
    } catch (...) {
        toggling_ducks_ = false;
    }

    toggling_ducks_ = false;
}

void
CanvasView::toggle_duck_mask_all()
{
    if (work_area->get_type_mask_state() == Duck::TYPE_NONE) {
        work_area->set_type_mask_state(work_area->get_type_mask());
        work_area->set_type_mask(Duck::TYPE_NONE);
        toggle_duck_mask(Duck::TYPE_NONE);
    } else {
        work_area->set_type_mask(work_area->get_type_mask_state());
        work_area->set_type_mask_state(Duck::TYPE_NONE);
        toggle_duck_mask(Duck::TYPE_NONE);
    }
}

void
CanvasView::mask_bone_ducks()
{
    Duck::Type mask(work_area->get_type_mask());
    bool recursive(mask & Duck::TYPE_BONE_RECURSIVE);

    if (recursive) {
        action_mask_bone_setup_ducks->set_active(true);
        action_mask_bone_recursive_ducks->set_active(false);
    } else {
        action_mask_bone_recursive_ducks->set_active(true);
    }
}

void
CanvasView::on_meta_data_changed()
{
    // update the buttons and actions that are associated
    toggling_show_grid = true;
    toggling_snap_grid = true;

    try {
        // Update the toggle ducks actions
        Glib::RefPtr<Gtk::ToggleAction> action;
        action = Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic(action_group->get_action("toggle-grid-show"));
        action->set_active((bool)(work_area->grid_status()));
        action = Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic(action_group->get_action("toggle-grid-snap"));
        action->set_active((bool)(work_area->get_grid_snap()));
        // Update the toggle buttons
        snap_grid->set_active(work_area->get_grid_snap());
        show_grid->set_active(work_area->grid_status());
    } catch (...) {
        toggling_show_grid = false;
        toggling_snap_grid = false;
    }

    toggling_show_grid = false;
    toggling_snap_grid = false;
}

void
CanvasView::image_import()
{
    String filename("*.*");
    String errors, warnings;

    if (App::dialog_open_file(_("Please select a file"), filename, IMAGE_DIR_PREFERENCE)) {
        canvas_interface()->import(filename, errors, warnings, App::resize_imported_images);

        if (warnings != "")
            App::dialog_message_1b(
                "WARNING",
                strprintf("%s:\n\n%s", _("Warning"), warnings.c_str()),
                "details",
                _("Close"));
    }
}

Smach::event_result
CanvasView::process_event_key(EventKey x)
{
    return smach_.process_event(x);
}

void
CanvasView::on_input_device_changed(GdkDevice* device)
{
    if (!device) {
        get_smach().egress();
    }

    assert(device);

    synfigapp::InputDevice::Handle input_device;
    input_device = synfigapp::Main::select_input_device(gdk_device_get_name(device));
    process_event_key(EVENT_INPUT_DEVICE_CHANGED);
}

void
CanvasView::on_preview_option()
{
    if (Canvas::Handle canv = get_canvas()) {
        RendDesc &r = canv->rend_desc();

        if (r.get_frame_rate()) {
            float rate = 1 / r.get_frame_rate();
            float beg = r.get_time_start() + r.get_frame_start() * rate;
            float end = r.get_time_start() + r.get_frame_end() * rate;

            Dialog_PreviewOptions *po = dynamic_cast<Dialog_PreviewOptions *>(get_ext_widget("prevoptions"));

            if (!po) {
                po = new Dialog_PreviewOptions();
                po->set_fps(r.get_frame_rate() / 2);
                set_ext_widget("prevoptions", po);
            }

            if (!po->get_begin_override()) {
                po->set_begintime(beg);
            }

            if (!po->get_end_override()) {
                po->set_endtime(end);
            }

            po->set_global_fps(r.get_frame_rate());
            po->signal_finish().connect(sigc::mem_fun(*this, &CanvasView::on_preview_create));
            po->present();
        }
    }
}

void
CanvasView::on_preview_create(const PreviewInfo &info)
{
    // set all the options
    etl::handle<Preview>	prev = new Preview;

    prev->set_canvasview(this);
    prev->set_zoom(info.zoom);
    prev->set_fps(info.fps);
    prev->set_overbegin(info.overbegin);
    prev->set_begintime(info.begintime);
    prev->set_overend(info.overend);
    prev->set_endtime(info.endtime);
    prev->set_quality(work_area->get_quality());
#ifdef WITH_JACK
    prev->set_jack_offset(get_jack_offset());
#endif

    // render it out...
    prev->render();

    preview_dialog.set_default_size(700, 510);
    preview_dialog.set_preview(prev.get());
    preview_dialog.present();

    // Preview Window created, the action can be enabled
    {
        Glib::RefPtr< Gtk::Action > action = action_group->get_action("dialog-flipbook");
        action->set_sensitive(true);
    }

}

void
CanvasView::on_audio_option()
{
    synfig::warning("Launching Audio Options");
    sound_dialog.set_global_fps(get_canvas()->rend_desc().get_frame_rate());
    sound_dialog.present();
}

void
CanvasView::on_audio_file_change(const std::string &f)
{
    // save in meta data - always even when not valid...
    canvas_interface()->set_meta_data("audiofile", f);
}

void
CanvasView::on_audio_offset_change(const synfig::Time &t)
{
    String s;
    {
        ChangeLocale change_locale(LC_NUMERIC, "C");
        s = t.get_string();
    }
    canvas_interface()->set_meta_data("audiooffset", s);
}

void
CanvasView::on_audio_file_notify()
{
    std::string file(get_canvas()->get_meta_data("audiofile"));

    if (!file.c_str()) {
        return;
    }

    if (!audio->load(file, dirname(get_canvas()->get_file_name()) + string("/"))) {
        if (file != "") {
            synfig::warning("Could not load the file: %s", file.c_str());
        }

        get_canvas()->erase_meta_data("audiofile");
        disp_audio->hide();
        disp_audio->set_profile(etl::handle<AudioProfile>());
    } else {
        // save in canvasview
        synfig::warning("Getting the profile of the music stuff");

        disp_audio->set_profile(audio->get_profile());
        disp_audio->show();

        synfig::warning("successfully set the profiles and stuff");
    }

    disp_audio->queue_draw();
}

void
CanvasView::on_audio_offset_notify()
{
    Time t;
    {
        ChangeLocale change_locale(LC_NUMERIC, "C");
        t = Time(get_canvas()->get_meta_data("audiooffset"), get_canvas()->rend_desc().get_frame_rate());
    }
    audio->set_offset(t);
    sound_dialog.set_offset(t);
    disp_audio->queue_draw();

}

void
CanvasView::play_audio(float t)
{
    if (audio.get()) {
        synfig::info("Playing audio at %f s", t);
        audio->play(t);
    }
}

void
CanvasView::stop_audio()
{
    if (audio.get()) {
        audio->stop();
    }
}

bool
CanvasView::on_audio_scrub()
{
    disp_audio->draw();
    return true;
}

Glib::RefPtr<Glib::ObjectBase>
CanvasView::get_ref_obj(const synfig::String& x)
{
    return ref_obj_book_[x];
}

Glib::RefPtr<const Glib::ObjectBase>
CanvasView::get_ref_obj(const synfig::String& x)const
{
    return ref_obj_book_.find(x)->second;
}

void
CanvasView::set_ref_obj(const synfig::String& x, Glib::RefPtr<Glib::ObjectBase> y)
{
    ref_obj_book_[x] = y;
}

Glib::RefPtr<Gtk::TreeModel>
CanvasView::get_tree_model(const synfig::String& x)
{
    return Glib::RefPtr<Gtk::TreeModel>::cast_dynamic(ref_obj_book_["_tree_model_" + x]);
}

Glib::RefPtr<const Gtk::TreeModel>
CanvasView::get_tree_model(const synfig::String& x)const
{
    return Glib::RefPtr<Gtk::TreeModel>::cast_dynamic(ref_obj_book_.find("_tree_model_" + x)->second);
}

void
CanvasView::set_tree_model(const synfig::String& x, Glib::RefPtr<Gtk::TreeModel> y)
{
    ref_obj_book_["_tree_model_" + x] = Glib::RefPtr<Glib::ObjectBase>::cast_static(y);
}

Gtk::Widget*
CanvasView::get_ext_widget(const synfig::String& x)
{
    return ext_widget_book_[x];
}

void
CanvasView::set_ext_widget(const synfig::String& x, Gtk::Widget* y)
{
    ext_widget_book_[x] = y;

    if (x == "layers_cmp") {
        layer_tree = dynamic_cast<LayerTree*>(y);

        layer_tree->get_selection()->signal_changed().connect(SLOT_EVENT(EVENT_LAYER_SELECTION_CHANGED));
        layer_tree->get_selection()->signal_changed().connect(SLOT_EVENT(EVENT_REFRESH_DUCKS));
        layer_tree->signal_layer_user_click().connect(sigc::mem_fun(*this, &studio::CanvasView::on_layer_user_click));
        layer_tree->signal_param_user_click().connect(sigc::mem_fun(*this, &studio::CanvasView::on_children_user_click));
        layer_tree->signal_waypoint_clicked_layertree().connect(sigc::mem_fun(*this, &studio::CanvasView::on_waypoint_clicked_canvasview));
    }

    if (x == "children") {
        children_tree = dynamic_cast<ChildrenTree*>(y);

        if (children_tree) {
            children_tree->signal_user_click().connect(sigc::mem_fun(*this, &studio::CanvasView::on_children_user_click));
        }

        if (children_tree) {
            children_tree->signal_waypoint_clicked_childrentree().connect(sigc::mem_fun(*this, &studio::CanvasView::on_waypoint_clicked_canvasview));
        }

        if (children_tree) {
            children_tree->get_selection()->signal_changed().connect(SLOT_EVENT(EVENT_REFRESH_DUCKS));
        }
    }

    if (x == "keyframes") {
        keyframe_tree = dynamic_cast<KeyframeTree*>(y);
    }
}

Gtk::UIManager::ui_merge_id
CanvasView::get_popup_id()
{
    return merge_id_popup_;
}

void
CanvasView::set_popup_id(Gtk::UIManager::ui_merge_id popup_id)
{
    merge_id_popup_ = popup_id;
}

Gtk::UIManager::ui_merge_id
CanvasView::get_toolbar_id()
{
    return merge_id_toolbar_;
}

void
CanvasView::set_toolbar_id(Gtk::UIManager::ui_merge_id toolbar_id)
{
    merge_id_toolbar_ = toolbar_id;
}

bool
CanvasView::on_delete_event(GdkEventAny* event __attribute__((unused)))
{
    close_view();

    // TODO:  This causes the window to be deleted straight away - but what if we prompt 'save?' and the user cancels?
    //		  Is there ever any need to pass on the delete event to the window here?

    return true;
}

// Modify the play stop button apearence and play stop the animation
void
CanvasView::on_play_pause_pressed()
{
    if (jack_enabled) {
#ifdef WITH_JACK

        if (jack_is_playing) {
            jack_transport_stop(jack_client);
            on_jack_sync();
            stop_async();
        } else {
            jack_transport_start(jack_client);
        }

#endif
    } else {
        if (!is_playing()) {
            play_async();
        } else {
            stop_async();
        }
    }
}

bool
CanvasView::is_time_equal_to_current_frame(const synfig::Time &time, const synfig::Time &range)
{
    float fps(get_canvas()->rend_desc().get_frame_rate());
    Time starttime = get_canvas()->rend_desc().get_time_start();
    Time endtime = get_canvas()->rend_desc().get_time_end();

    synfig::Time t0 = get_time();
    synfig::Time t1 = time;

    if (fps != 0.f) {
        t0 = t0.round(fps);
        t1 = t1.round(fps);
    }

    t0 = std::max(starttime, std::min(endtime, t0));
    t1 = std::max(starttime, std::min(endtime, t1));
    double epsilon = max(range, Time::epsilon());
    double dt0 = t0;
    double dt1 = t1;

    return abs(dt0 - dt1) <= epsilon;
}

#ifdef WITH_JACK
void
CanvasView::toggle_jack_button()
{
    if (!toggling_jack) {
        string message;
        string details;

        if (get_jack_enabled()) {
            message = strprintf(_("Are you sure you want to disable JACK synchronization?"));
            details = strprintf(_("The JACK server will remain running."));
        } else {
            message = strprintf(_("Are you sure you want to enable JACK synchronization?"));
            details = strprintf(_("This operation will launch a JACK server, if it isn't started yet."));
        }

        int answer = get_ui_interface()->confirmation(
                         message,
                         details,
                         _("No"),
                         _("Yes"),
                         synfigapp::UIInterface::RESPONSE_OK);

        if (answer == synfigapp::UIInterface::RESPONSE_OK) {
            set_jack_enabled(!get_jack_enabled());
        }

        // Update button state
        toggling_jack = true;
        jackdial->get_toggle_jackbutton()->set_active(get_jack_enabled());
        toggling_jack = false;
    }
}

void
CanvasView::on_jack_offset_changed()
{
    set_jack_offset(jackdial->get_offset());

    if (get_jack_enabled()) {
        on_jack_sync();
    }
}

synfig::Time
CanvasView::get_jack_offset()const
{
    return work_area->get_jack_offset();
}

void
CanvasView::set_jack_offset(const synfig::Time &value)
{
    work_area->set_jack_offset(value);
}

void
CanvasView::on_jack_sync()
{
    jack_position_t pos;
    jack_transport_state_t state = jack_transport_query(jack_client, &pos);

    jack_is_playing = state == JackTransportRolling || state == JackTransportStarting;
    jack_time = Time((Time::value_type)pos.frame / (Time::value_type)pos.frame_rate);

    if (is_playing() != jack_is_playing) {
        if (jack_is_playing) {
            play_async();
        } else {
            stop_async();
        }
    }

    if (!is_time_equal_to_current_frame(jack_time - get_jack_offset())) {
        jack_synchronizing = true;
        set_time(jack_time - get_jack_offset());
        time_adjustment()->set_value(get_time());
        time_adjustment()->value_changed();
        jack_synchronizing = false;
    }
}

int
CanvasView::jack_sync_callback(jack_transport_state_t /* state */, jack_position_t * /* pos */, void *arg)
{
    CanvasView *canvasView = static_cast<CanvasView*>(arg);
    canvasView->jack_dispatcher.emit();
    return 1;
}
#endif

void
CanvasView::interpolation_refresh()
{
    widget_interpolation->set_value(synfigapp::Main::get_interpolation());
}

void
CanvasView::on_interpolation_changed()
{
    synfigapp::Main::set_interpolation(Waypoint::Interpolation(widget_interpolation->get_value()));
}

void
CanvasView::on_interpolation_event(GdkEvent * /* event */)
{
    widget_interpolation_scroll->get_hscrollbar()->get_adjustment()->set_value(0);
}
